Bregman divergences (BD)
Definition Let
\(\phi:\mathcal{C}\rightarrow\mathbb{R}\) be
a strictly convex and continuously differentiable function defined on a
measurable convex subset \(\mathcal{C}\subset\mathbb{R}^d\) . Let \(int(\mathcal{C})\) denote its relative
interior. A Bregman divergence indexed by \(\phi\) is a dissimilarity measure \(d_{\phi}:\mathcal{C}\times
int(\mathcal{C})\rightarrow\mathbb{R}\) defined for any pair
\((x,y)\in \mathcal{C}\times
int(\mathcal{C})\) by, \[\begin{equation}
\label{eq:1.10}
d_{\phi}(x,y)=\phi(x)-\phi(y)-\langle x-y,\nabla\phi(y)\rangle
\end{equation}\] where \(\nabla\phi(y)\) denotes the gradient of
\(\phi\) computed at a point \(y\in int(\mathcal{C})\) . A Bregman
divergence is not necessarily a metric as it may not be symmetric and
the triangular inequality might not be satisfied.
This section defines all the Bregman divergences used. The list of
all the Bregman divergences is given in the table below:
Euclidean
\({\|x\|_2^2}=\sum_{i=1}^dx_i^2\)
\(\|x-y\|_2^2\)
\(\mathbb{R}^d\)
General Kullback-Leibler
\(\sum_{i=1}^d x_i\ln(
x_i)\)
\(\sum_{i=1}^d( x_i\ln(\frac{
x_i}{y_i})-(x_i-y_i))\)
\((0,+\infty)^d\)
Logistic
\(\sum_{i=1}^d(x_i\ln(
x_i)+(1- x_i)\ln(1- x_i))\)
\(\sum_{i=1}^d\Big(
x_i\ln(\frac{x_i}{y_i})+(1- x_i)\ln(\frac{1-
x_i}{1-y_i})\Big)\)
\((0,1)^d\)
Itakura-Saito
\(-\sum_{i=1}^d\ln(
x_i)\)
\(\sum_{i=1}^d\Big(\frac{
x_i}{y_i}-\ln(\frac{ x_i}{y_i})-1\Big)\)
\((0,+\infty)^d\)
Exponential
\(\sum_{i=1}^de^{x_i}\)
\(\sum_{i=1}^d(e^{x_i}-e^{y_i}-e^{y_i}(x_i-y_i))\)
\(\mathbb{R}^d\)
Polynomial
\(\sum_{i=1}^d|x|^p,p>2\)
\(\sum_{k=1}^d(|x_k|^p-|y_k|^p-\text{sign}(y_k)^pp(x_k-y_k)y_k^{p-1})\)
\(\mathbb{R}^d\)
Look-up list of Bregman
divergences
The codes below provide a look-up list of all the BDs defined in the
table above.
euclidDiv <- function(X., y., deg = NULL){
res <- sweep(X., 2, y.)
return(rowSums(res^2))
}
gklDiv <- function(X., y., deg = NULL){
res <- c("/", "-") %>%
map(.f = ~ sweep(X., 2, y., FUN = .x))
return(rowSums(X.*log(res[[1]]) - res[[2]]))
}
logDiv <- function(X., y., deg = NULL){
res <- map2(.x = list(X., 1-X.),
.y = list(y., 1-y.),
.f = ~ sweep(.x, 2, .y, FUN = "/"))
return(rowSums(X.*log(res[[1]])+(1-X.)*log(res[[2]])))
}
itaDiv <- function(X., y., deg = NULL){
res <- sweep(X., 2, y., FUN = "/")
return(rowSums(res-log(res) - 1))
}
expDiv <- function(X., y., deg = NULL){
exp_y <- exp(y.)
res <- sweep(1+X., 2, y.) %>%
sweep(2, exp_y, FUN = "*")
return(rowSums(exp(X.)-res))
}
polyDiv <- function(X., y., deg = 3){
S <- map2(.x = list(X., X.^deg),
.y = list(y., y.^deg),
.f = ~ sweep(.x,
MARGIN = 2,
STATS = .y,
FUN = "-"))
if(deg %% 2 == 0){
Tem <- sweep(S[[1]], 2, y.^(deg-1), FUN = "*")
res <- rowSums(S[[2]] - deg * Tem)
}
else{
Tem <- sweep(S[[1]], 2, sign(y.) * y.^(deg-1), FUN = "*")
res <- rowSums(S[[2]] - deg * Tem)
}
return(res)
}
lookup_div <- list(
euclidean = euclidDiv,
gkl = gklDiv,
logistic = logDiv,
itakura = itaDiv,
exponential = expDiv,
polynomial = polyDiv
)
Function :
BregmanDiv
This function computes the matrix of BDs between two sets of data
points. Each set of data points should be stored as matrices, data
frame, or tibble object where each row represents an individual data
point.
BregmanDiv <- function(X.,
C.,
div = c("euclidean",
"gkl",
"logistic",
"itakura",
"exponential",
"polynomial"),
deg = 3){
div <- match.arg(div)
d_c <- dim(C.)
if(is.null(d_c)){
C <- matrix(C., nrow = 1, byrow = TRUE)
} else{
C <- as.matrix(C.)
}
if(is.null(dim(X.))){
X <- matrix(X., nrow = 1, byrow = TRUE)
} else{
X <- as.matrix(X.)
}
dis <- map_dfc(.x = 1:dim(C)[1],
.f = ~ tibble('{{.x}}' := lookup_div[[div]](X, C[.x,], deg = deg)))
return(dis)
}
Step \(K\) :
\(K\) -means with Bregman
divergences
This section implements \(K\) -means
algorithm using Bregman divergences which corresponds to the step \(K\) of KFC procedure.
Function :
findClosestCentroid and newCentroids
These two functions perform the main steps of \(K\) -means algorithm.
findClosestCentroid function assigns any data points to
some cluster according to the nearest centroid among all the centroids,
and newCentroids function compute the new centroids of the
partition given the cluster labels of all data points.
findClosestCentroid <- function(x., centroids., div, deg = 3){
dist <- BregmanDiv(x., centroids., div, deg)
clust <- 1:nrow(x.) %>%
map_int(.f = ~ which.min(dist[.x,]))
return(clust)
}
newCentroids <- function(x., clusters.){
centroids <- unique(clusters.) %>%
map_dfr(.f = ~ colMeans(x.[clusters. == .x, ]))
return(centroids)
}
Function :
kmeansBD
This function performs \(K\) -means
algorithm with BDs.
kmeansBD <- function(train_input,
K,
n_start = 5,
maxIter = 500,
deg = 3,
scale_input = FALSE,
div = "euclidean",
splits = 1,
epsilon = 1e-10,
center_ = NULL,
scale_ = NULL,
id_shuffle = NULL){
start_time <- Sys.time()
# Distortion function
X <- as.matrix(train_input)
N <- dim(X)
if(scale_input){
if(!(is.null(center_) & is.null(scale_))){
if(length(center_) == 1){
center_ <- rep(center_, N[2])
}
if(length(scale_) == 1){
scale_ <- rep(scale_, N[2])
}
} else{
min_ <- apply(X, 2, FUN = min)
c_ <- abs(colMeans(X)/5)
center_ <- min_ - c_
scale_ <- apply(X, 2, FUN = max) - center_ + 1
}
X <- scale(X, center = center_, scale = scale_)
}
if(is.null(id_shuffle)){
train_id <- rep(TRUE, N[1])
if(splits < 1){
train_id[sample(N[1], floor(N[1]*(1-splits)))] <- FALSE
}
} else{
train_id <- id_shuffle
}
X_train1 <- X[train_id,]
X_train2 <- X[!train_id,]
mu <- as.matrix(colMeans(X_train1))
distortion <- function(clus){
cent <- newCentroids(X_train1, clus)
var_within <- 1:K %>%
map(.f = ~ BregmanDiv(X_train1[clus == .x,],
cent[.x,],
div,
deg)) %>%
map(.f = sum) %>%
Reduce("+", .)
return(var_within)
}
# Kmeans algorithm
kmeansWithBD <- function(x., k., maxiter., eps.) {
n. <- nrow(x.)
# initialization
init <- sample(n., k.)
centroids_old <- x.[init,]
i <- 0
while(i < maxIter){
# Assignment step
clusters <- findClosestCentroid(x., centroids_old, div, deg)
# Recompute centroids
centroids_new <- newCentroids(x., clusters)
if ((sum(is.na(centroids_new)) > 0) |
(nrow(centroids_new) != k.)) {
init <- sample(n., k.)
centroids_old <- x.[init,]
warning("NA produced -> reinitialize centroids...!")
}
else{
if(sum(abs(centroids_new - centroids_old)) > eps.){
centroids_old <- centroids_new
} else{
break
}
}
i <- i + 1
}
return(clusters)
}
results <- 1:n_start %>%
map_dfc(.f = ~ tibble("{{.x}}" := kmeansWithBD(X_train1,
K,
maxIter,
epsilon)))
opt_id <- 1:n_start %>%
map_dbl(.f = ~ distortion(results[[.x]])) %>%
which.min
cluster <- clusters <- results[[opt_id]]
j <- 1
ID <- unique(cluster)
for (i in ID) {
clusters[cluster == i] = j
j = j + 1
}
centroids = newCentroids(X_train1, clusters)
time_taken <- Sys.time() - start_time
return(
list(
centroids = centroids,
clusters = clusters,
train_data = list(X_train = X_train1,
X_remain = X_train2,
id_remain = !train_id),
parameters = list(div = div,
deg = deg,
center_ = center_,
scale_ = scale_),
running_time = time_taken
)
)
}
Example.1 : We perform \(K\) -means algorithm with "gkl"
BD on Abalone
dataset.
pacman::p_load(readr)
colname <- c("Type", "LongestShell", "Diameter", "Height", "WholeWeight", "ShuckedWeight", "VisceraWeight", "ShellWeight", "Rings")
df <- readr::read_delim("https://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data", col_names = colname, delim = ",", show_col_types = FALSE)
n <- nrow(df)
train <- logical(n)
train[sample(n, floor(n*0.8))] <- TRUE
cl <- df[train,2:(ncol(df)-1)] %>%
kmeansBD(K = 3, div = "gkl", splits = 0.5, scale_input = TRUE)
table(cl$clusters)
1 2 3
435 697 539
Step \(F\) :
Fitting predictive models
This section builds global models by fitting local model on each
given cluster of the obtained partition. This corresponds to the step
\(F\) of the procedure.
Function :
fitLocalModels
This function fits local models on all clusters of the obtained
partition.
fitLocalModels <- function(kmeans_BD,
train_response,
model = "lm",
formula = NULL){
start_time <- Sys.time()
X_train <- kmeans_BD$train_data$X_train
y_train <- train_response[!(kmeans_BD$train_data$id_remain)]
X_remain <- kmeans_BD$train_data$X_remain
y_remain <- NULL
if(!is.null(X_remain)){
y_remain <- train_response[kmeans_BD$train_data$id_remain]
}
pacman::p_load(tree)
pacman::p_load(randomForest)
model_ <- ifelse(model == "tree", tree::tree, model)
K <- nrow(kmeans_BD$centroids)
if (is.null(formula)){
form <- formula(target ~ .)
}
else{
form <- update(formula, target ~ .)
}
data_ <- bind_cols(X_train, "target":= y_train)
fit_lookup <- list(lm = "fitted.values",
rf = "predicted")
if(is.character(model_)){
model_lookup <- list(lm = lm,
rf = randomForest::randomForest)
mod <- map(.x = 1:K,
.f = ~ model_lookup[[model_]](formula = form,
data = data_[kmeans_BD$clusters == .x, ]))
} else{
mod <- map(.x = 1:K,
.f = ~ model_(formula = form,
data = data_[kmeans_BD$clusters == .x,]))
}
pred0 <- NULL
if(!is.null(X_remain)){
pred0 <- vector(mode = "numeric",
length = length(y_remain))
clus <- findClosestCentroid(x. = X_remain,
centroids. = kmeans_BD$centroids,
div = kmeans_BD$parameters$div,
deg = kmeans_BD$parameters$deg)
for(i_ in 1:K){
pred0[clus == i_] <- predict(mod[[i_]],
as.data.frame(X_remain[clus == i_,]))
}
}
time_taken <- Sys.time() - start_time
return(list(
local_models = mod,
kmeans_BD = kmeans_BD,
data_remain = list(fit = pred0,
response = y_remain),
running_time = time_taken
))
}
Example.2 : From Example.1 above,
multiple linear regression models are built on all the obtained
clusters. The mean square error of this model, evaluated on the
remaining \(50\%\) of the training data
is computed.
fit <- fitLocalModels(train_response = df$Rings[train],
kmeans_BD = cl,
model = "lm")
mean((fit$data_remain$response- fit$data_remain$fit)^2)
[1] 4.71925
Function :
localPredict
This function allows us to predict any new observation using the
candidate model \(\cal M_j=({\cal
M}_{j,k})_{k=1}^M\) corresponding to Bregman divergence \({\cal B}_j\) , for some \(j\in J\subset\{1,...,M\}\) .
localPredict <- function(localModels,
newData){
kmean_BD <- localModels$kmeans_BD
K <- nrow(kmean_BD$centroids)
newData_ <- newData
if(!(is.null(kmean_BD$parameters$center_))){
newData_ <- scale(newData,
center = kmean_BD$parameters$center_,
scale = kmean_BD$parameters$scale_)
id0 <- newData_ <= 0
if(any(id0)){
min_ <- min(newData_[id0])
newData_[id0] <- runif(sum(id0), min(1e-3, min_/10), min_)
}
}
clus <- findClosestCentroid(x. = newData_,
centroids. = kmean_BD$centroids,
div = kmean_BD$parameters$div,
deg = kmean_BD$parameters$deg)
pred0 <- vector(mode = "numeric", length = nrow(newData_))
for(i_ in 1:K){
pred0[clus == i_] <- predict(localModels$local_models[[i_]],
as.data.frame(newData_[clus == i_,]))
}
pred0 <- as_tibble(pred0)
names(pred0) <- ifelse(kmean_BD$parameters$div == "polynomial",
paste0("polynomial", kmean_BD$parameters$deg),
kmean_BD$parameters$div)
return(pred0)
}
Example.3 : The the performance of the candidate
model corresponding to "gkl" divergence is compared to
random forest regression on a \(20\%\)
testing data.
y_hat <- localPredict(fit,
df[!train, 2:(ncol(df)-1),])
rf <- randomForest(Rings ~ ., data = df[train,2:ncol(df)])
mean((predict(rf, newdata = df[!train,2:ncol(df)])- df$Rings[!train])^2)
[1] 4.290698
mean((y_hat$gkl-df$Rings[!train])^2)
[1] 4.072496
Step \(C\) :
Combining methods
The aggregation methods are available here
.
The aggregation methods are imported into
Rstudio environment.
pacman::p_load(devtools)
### Kernel based consensual aggregation
source_url("https://raw.githubusercontent.com/hassothea/AggregationMethods/main/KernelAggReg.R")
ℹ SHA-1 hash of file is a8312879ba2ed0c5335566dd75fb90751025953c
### MixCobra
source_url("https://raw.githubusercontent.com/hassothea/AggregationMethods/main/MixCobraReg.R")
ℹ SHA-1 hash of file is 63f73453cd5d3e7006bd02ee84e8115300a20178
Function : stepK,
stepF and stepC
These functions allow to set the values of the parameters in the
three steps of the KFC procedure. Each function returns a list of all
the parameters given in its arguments.
stepK = function(K,
n_start = 5,
maxIter = 300,
deg = 3,
scale_input = FALSE,
div = NULL,
splits = 0.75,
epsilon = 1e-10,
center_ = NULL,
scale_ = NULL){
return(list(K = K,
n_start = n_start,
maxIter = maxIter,
deg = deg,
scale_input = scale_input,
div = div,
splits = splits,
epsilon = epsilon,
center_ = center_ ,
scale_ = scale_))
}
stepF = function(formula = NULL,
model = "lm"){
return(list(formula = formula,
model = model))
}
stepC = function(n_cv = 5,
method = c("cobra", "mixcobra"),
opt_methods = c("grad", "grid"),
kernels = "gaussian",
scale_features = FALSE){
return(list(n_cv = n_cv,
method = method,
opt_methods = opt_methods,
kernels = kernels,
scale_features = scale_features))
}
Function : KFCreg
This function is the complete implementation of KFC procedure.
Remark.2 : The parallel argument above
requires internet connection to load the source codes of \(K\) -means algorithm with BDs from GitHub
.
It is performed on the maximum number of clusters of your machine, and
the speed is at least two times faster than without parallelism,
however, it is not so stable depending on your internet connection or
machine. About the aggregation methods of step \(C\) ,
\(^1\) is the kernel-based consensual
aggregation for regression by Has (2021) .
The source codes of these functions are available in KernelAggReg.R ,
and the documentation is available here .
\(^2\) is the aggregation using
input-output trade-off by Fischer
and Mougeot (2019) . The source codes of these functions are
available in MixCobra.R ,
and the documentation is available on here .
KFCreg = function(train_input,
train_response,
test_input,
test_response = NULL,
n_cv = 5,
parallel = TRUE,
inv_sigma = sqrt(.5),
alp = 2,
K_step = stepK(splits = .5),
F_step = stepF(),
C_step = stepC(),
setGradParamAgg = setGradParameter(),
setGridParamAgg = setGridParameter(),
setGradParamMix = setGradParameter_Mix(),
setGridParamMix = setGridParameter_Mix()){
start_time <- Sys.time()
lookup_div_names <- c("euclidean",
"gkl",
"logistic",
"itakura",
"polynomial")
div_ <- K_step$div
### K step: Kmeans clustering with BDs
if (is.null(K_step$div)){
divergences <- lookup_div_names
warning("No divergence provided! All of them are used!")
}
else{
divergences <- K_step$div %>%
map_chr(.f = ~ match.arg(arg = .x,
choices = lookup_div_names))
}
div_list <- divergences %>%
map(.f = (\(x) if(x != "polynomial") return(x) else return(rep("polynomial", length(K_step$deg))))) %>%
unlist
deg_list <- rep(NA, length(div_))
deg_list[div_list == "polynomial"] <- K_step$deg
div_names <- map2_chr(.x = div_list,
.y = deg_list,
.f = (\(x, y) if(is.na(y)) return(x) else return(paste0(x,y))))
### Step K: Kmeans clustering with Bregman divergences
dm <- dim(train_input)
id_shuffle <- vector(length = dm[1])
n_train <- floor(K_step$splits * dm[1])
id_shuffle[sample(dm[1], n_train)] <- TRUE
if(parallel){
numCores <- parallel::detectCores()
doParallel::registerDoParallel(numCores) # use multicore, set to the number of our cores
kmean_ <- foreach(i=1:length(div_names)) %dopar% {
devtools::source_url("https://raw.githubusercontent.com/hassothea/KFC-Procedure/master/kmeanBD.R")
kmeansBD(train_input = train_input,
K = K_step$K,
div = div_list[i],
n_start = K_step$n_start,
maxIter = K_step$maxIter,
deg = deg_list[i],
scale_input = K_step$scale_input,
splits = K_step$splits,
epsilon = K_step$epsilon,
center_ = K_step$center_,
scale_ = K_step$scale_,
id_shuffle = id_shuffle)
}
doParallel::stopImplicitCluster()
} else{
kmean_ <- map2(.x = div_list,
.y = deg_list,
.f = ~ kmeansBD(train_input = train_input,
K = K_step$K,
div = .x,
n_start = K_step$n_start,
maxIter = K_step$maxIter,
deg = .y,
scale_input = K_step$scale_input,
splits = K_step$splits,
epsilon = K_step$epsilon,
center_ = K_step$center_,
scale_ = K_step$scale_,
id_shuffle = id_shuffle))
}
names(kmean_) <- div_names
### F step: Fitting the corresponding model on each observed cluster
model_ <- div_names %>%
map(.f = ~ fitLocalModels(kmean_[[.x]],
train_response = train_response,
model = F_step$model,
formula = F_step$formula))
names(model_) <- div_names
pred_combine <- model_ %>%
map_dfc(.f = ~ .x$data_remain$fit)
y_remain <- train_response[!id_shuffle]
pred_test <- div_names %>%
map_dfc(.f = ~ localPredict(model_[[.x]],
test_input))
names(pred_test) <- names(pred_combine) <- div_names
# C step: Consensual regression aggregation method with kernel-based COBRA
list_method_agg <- list(mixcobra = function(pred){MixCobraReg(train_input = train_input[!id_shuffle,],
train_response = y_remain,
test_input = test_input,
train_predictions = pred,
test_predictions = pred_test,
test_response = test_response,
scale_input = K_step$scale_input,
scale_machine = C_step$scale_features,
n_cv = C_step$n_cv,
inv_sigma = inv_sigma,
alp = alp,
kernels = C_step$kernels,
optimizeMethod = C_step$opt_methods,
setGradParam = setGradParamMix,
setGridParam = setGridParamMix)},
cobra = function(pred){kernelAggReg(train_design = pred,
train_response = y_remain,
test_design = pred_test,
test_response = test_response,
scale_input = K_step$scale_input,
scale_machine = C_step$scale_features,
build_machine = FALSE,
machines = NULL,
n_cv = C_step$n_cv,
inv_sigma = sqrt(.5),
alp = 2,
kernels = C_step$kernels,
optimizeMethod = C_step$opt_methods,
setGradParam = setGradParamAgg,
setGridParam = setGridParamAgg)})
res <- map(.x = C_step$method,
.f = ~ list_method_agg[[.x]](pred_combine))
list_agg_methods <- list(cobra = "cob",
mixcobra = "mix")
names(res) <- C_step$method
ext_fun <- function(L, nam){
tab <- L$fitted_aggregate
names(tab) <- paste0(names(tab), "_", nam)
return(tab)
}
pred_fin <- C_step$method %>%
map_dfc(.f = ~ ext_fun(res[[.x]], list_agg_methods[[.x]]))
time.taken <- Sys.time() - start_time
### To finish
if(is.null(test_response)){
return(list(
predict_final = pred_fin,
predict_local = pred_test,
agg_method = res,
running_time = time.taken
))
} else{
error <- cbind(pred_test, pred_fin) %>%
dplyr::mutate(y_test = test_response) %>%
dplyr::summarise_all(.funs = ~ (. - y_test)) %>%
dplyr::select(-y_test) %>%
dplyr::summarise_all(.funs = ~ mean(.^2))
return(list(
predict_final = pred_fin,
predict_local = pred_test,
agg_method = res,
mse = error,
running_time = time.taken
))
}
}
Example.4 : A complete KFC procedure is implemented
on the same Abalone data, using \(6\)
BDs "euclidean", "itakura",
"gkl", "logistic" and
"polynomial" (of degree \(3\) and \(6\) ). Both aggregation methods are used in
the step \(C\) . Two kernel functions
are used for each aggregation method: "gaussian" (with
gradient descent algorithm) and "epanechnikov" (with grid
search algorithm).
train1 <- logical(n)
train1[sample(n, floor(n*0.8))] <- TRUE
kfc1 <- KFCreg(train_input = df[train1,2:ncol(df)],
train_response = df$Rings[train1],
test_input = df[!train1,2:ncol(df)],
K_step = stepK(K = 3,
scale_input = TRUE,
div = c("eucl", "ita", "gkl", "log" ,"poly"),
deg = c(3, 6),
splits = .5),
C_step = stepC(method = c("cobra", "mixcobra"),
opt_methods = c("grad", "grid"),
kernels = c("gaussian", "gaussian"),
scale_features = FALSE),
setGradParamAgg = setGradParameter(rate = 1),
setGridParamAgg = setGridParameter(min_val = .00001,
max_val = 10,
n_val = 100),
setGradParamMix = setGradParameter_Mix(rate = c(1,1)),
setGridParamMix = setGridParameter_Mix(min_alpha = 1e-10,
max_alpha = 5,
min_beta = 1e-10,
max_beta = 10,
n_alpha = 20,
n_beta = 20))
Kernel-based consensual aggregation method
------------------------------------------
* Gradient descent algorithm ...
Step | Parameter | Gradient | Threshold
---------------------------------------------------
0 | 23.3334 | -2.75e-11 | 1e-10
---------------------------------------------------
1 | 24.3334 | -1.742e-10 | 0.0428571
2 | 30.6667 | 1.467e-10 | 0.2602737
3 | 25.3334 | -1.1e-10 | 0.1739129
4 | 29.3334 | 2.567e-10 | 0.1578946
5 | 20.0000 | -2.75e-11 | 0.3181816
6 | 21.0000 | 2.108e-10 | 0.04999994
7 | 13.3334 | 1.1e-10 | 0.365079
8 | 11.3334 | 0e+00 | 0.1499998
-------------------------------------------------------
Stopped| 11.3334 | 0 | 0
~ Observed parameter: 11.33336 in 8 iterations.
* Grid search algorithm...
~ Observed parameter : 7.47475
MixCobra for regression
-----------------------
* Gradient descent algorithm ...
Step | alpha ; beta | Gradient (alpha ; beta) | Threshold
--------------------------------------------------------------------------------
0 | 7.50002 ; 37.52500 | -2.292e-10 ; -1.1e-10 | 1e-10
--------------------------------------------------------------------------------
1 | 8.5000 ; 38.5250 | -1.467e-10 ; 0e+00 | 0.04441974
2 | 9.1400 ; 38.5250 | -1.375e-10 ; -2.75e-11 | 0.01360977
3 | 9.7400 ; 38.7750 | 2.75e-11 ; -3.667e-11 | 0.01783278
4 | 9.6200 ; 39.1083 | -2.75e-11 ; -1.833e-10 | 0.009344184
5 | 9.7400 ; 40.7750 | -9.167e-11 ; -8.25e-11 | 0.03666585
6 | 10.1400 ; 41.5250 | -1.1e-10 ; 1.1e-10 | 0.0227655
7 | 10.6200 ; 40.5250 | -2.75e-11 ; 2.75e-11 | 0.02864607
8 | 10.7400 ; 40.4000 | 3.667e-11 ; -2.292e-10 | 0.0047903
9 | 10.5800 ; 41.4417 | 0e+00 ; 1.375e-10 | 0.02349758
10 | 10.5800 ; 41.1292 | -1.375e-10 ; -3.759e-10 | 0.00600711
11 | 10.7300 ; 41.5562 | 1.1e-10 ; 2.017e-10 | 0.01116017
12 | 10.6700 ; 41.4417 | -1.1e-10 ; 0e+00 | 0.00333899
13 | 10.7000 ; 41.4417 | -1.192e-10 ; -2.75e-11 | 0.0005756866
14 | 10.7163 ; 41.4456 | -1.1e-10 ; 0e+00 | 0.0003865669
15 | 10.7313 ; 41.4456 | -2.75e-11 ; 3.667e-11 | 0.0002875665
16 | 10.7350 ; 41.4443 | -1.1e-10 ; -1.1e-10 | 9.682615e-05
17 | 10.7500 ; 41.4462 | 1.192e-10 ; 2.108e-10 | 0.0003249014
18 | 10.7338 ; 41.4444 | 0e+00 ; -9.167e-11 | 0.0003471848
19 | 10.7338 ; 41.4448 | -1.558e-10 ; -1.1e-10 | 7.798307e-06
20 | 10.7391 ; 41.4450 | -3.667e-11 ; 5.5e-11 | 0.0001064928
21 | 10.7397 ; 41.4449 | 9.167e-11 ; 2.75e-11 | 1.431605e-05
22 | 10.7382 ; 41.4449 | 0e+00 ; -1.833e-10 | 3.052659e-05
23 | 10.7382 ; 41.4451 | -1.1e-10 ; -3.667e-11 | 3.898789e-06
24 | 10.7386 ; 41.4451 | -1.375e-10 ; 9.167e-11 | 9.372653e-06
25 | 10.7389 ; 41.4450 | -1.192e-10 ; -3.667e-11 | 6.588866e-06
26 | 10.7392 ; 41.4450 | -1.192e-10 ; 3.667e-11 | 5.060538e-06
27 | 10.7394 ; 41.4450 | 1.192e-10 ; 2.75e-11 | 4.963045e-06
28 | 10.7392 ; 41.4450 | -3.667e-11 ; 6.417e-11 | 4.902104e-06
29 | 10.7392 ; 41.4450 | 2.75e-11 ; 7.334e-11 | 8.338345e-07
30 | 10.7392 ; 41.4450 | 2.75e-11 ; 1.192e-10 | 3.781737e-07
31 | 10.7392 ; 41.4450 | -1.1e-10 ; -9.167e-11 | 2.987378e-07
32 | 10.7392 ; 41.4450 | -1.467e-10 ; -1.467e-10 | 6.832472e-07
33 | 10.7392 ; 41.4450 | -1.1e-10 ; 3.667e-11 | 4.717424e-07
34 | 10.7392 ; 41.4450 | -9.167e-11 ; 2.75e-11 | 3.050729e-07
35 | 10.7393 ; 41.4450 | -1.558e-10 ; -2.108e-10 | 2.430593e-07
36 | 10.7393 ; 41.4450 | 3.667e-11 ; 0e+00 | 4.677214e-07
37 | 10.7393 ; 41.4450 | 1.375e-10 ; 1.467e-10 | 9.356861e-08
38 | 10.7393 ; 41.4450 | -1.192e-10 ; -3.667e-11 | 1.876246e-07
39 | 10.7393 ; 41.4450 | 0e+00 ; 0e+00 | 1.53572e-07
40 | 10.7393 ; 41.4450 | 0e+00 ; 0e+00 | 1.558412e-10
--------------------------------------------------------------------------------
Stopped| 10.7393 ; 41.4450 | 0 | 0
~ Observed parameter: (alpha, beta) = ( 10.73927 , 41.44502 ) in 40 itertaions.
* Grid search algorithm...
~ Observed parameter: (alpha, beta) = (1e-10, 7.368421)
The mean square errors evaluated on \(20\%\) -testing data of the above
computation are reported below.
kfc1$predict_final %>%
mutate(rf = predict(rf1, newdata = df[!train1,2:ncol(df)])) %>%
sweep(MARGIN = 1, STATS = df$Rings[!train1], FUN = "-") %>%
.^2 %>%
colMeans
gaussian_grad_cob gaussian_grid_cob gaussian_grad_mix gaussian_grid_mix rf
6.453406e-28 2.144367e-20 1.803888e+00 5.635831e+00 4.603206e+00
📖 Read also KernelAggReg and MixCobraReg .
LS0tDQp0aXRsZTogIjxzcGFuIHN0eWxlPSdjb2xvcjogIzFDODFBQTsnPioqS0ZDIHByb2NlZHVyZSBmb3IgcmVncmVzc2lvbiAtPC9zcGFuPiBbSGFzIGV0IGFsLiAoMjAyMSldKGh0dHBzOi8vd3d3LnRhbmRmb25saW5lLmNvbS9lcHJpbnQvWUtHUzhHVEtEQktZRlhFR0ZXU0IvZnVsbD90YXJnZXQ9MTAuMTA4MC8wMDk0OTY1NS4yMDIxLjE4OTE1MzkpKioiDQphdXRob3I6ICI8c3BhbiBzdHlsZT0nY29sb3I6ICNENEE1MUM7Jz4qKipTb3RoZWEgSGFzKioqPC9zcGFuPiINCmRhdGU6ICI1LzIwLzIwMjIiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY3NzOiBoaWRlT3V0cHV0LmNzcw0KICAgIGluY2x1ZGVzOg0KICAgICAgaW5faGVhZGVyOiBoaWRlT3V0cHV0LnNjcmlwdA0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICcyJw0KICAgIHRvY2RlcHRoOiAyDQogIGh0bWxfbm90ZWJvb2s6DQogICAgY3NzOiBoaWRlT3V0cHV0LmNzcw0KICAgIGluY2x1ZGVzOg0KICAgICAgaW5faGVhZGVyOiBoaWRlT3V0cHV0LnNjcmlwdA0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDINCiAgICB0b2NkZXB0aDogMg0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICcyJw0KLS0tDQoNCjxzdHlsZT4NCiAgLmJ0biB7DQogICAgYm9yZGVyLXdpZHRoOiAwIDBweCAwcHggMHB4Ow0KICAgIGZvbnQtd2VpZ2h0OiBub3JtYWw7DQogICAgdGV4dC10cmFuc2Zvcm06IDsNCiAgfQ0KLmJ0bi1kZWZhdWx0IHsNCiAgY29sb3I6ICMyZWNjNzE7DQogICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZmZmZjsNCiAgICBib3JkZXItY29sb3I6ICNmZmZmZmY7DQp9DQo8L3N0eWxlPg0KDQo8IS0tIENvbG9ycw0KYmx1ZSA6ICMxRkFBRTMNCnllbGxvdyA6ICNGMEFFMTQNCmdyZWVuIDogIzU0RDMxOSANCnJlZCA6ICNFNjE4MEENCi0tPg0KDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KIyBDaGVjayBpZiBwYWNrYWdlICJmb250YXdlc29tZSIgaXMgYWxyZWFkeSBpbnN0YWxsZWQgDQoNCmxvb2t1cF9wYWNrYWdlcyA8LSBpbnN0YWxsZWQucGFja2FnZXMoKVssMV0NCmlmKCEoImZvbnRhd2Vzb21lIiAlaW4lIGxvb2t1cF9wYWNrYWdlcykpDQogIGluc3RhbGwucGFja2FnZXMoImZvbnRhd2Vzb21lIikNCmBgYA0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPiYjMTI4MjcwOzx1PiBIb3cgdG8gZG93bmxvYWQgJiBydW4gdGhlIGNvZGVzPzwvdT48L3NwYW4+ey19DQo9PT0NCg0KQWxsIHRoZSBzb3VyY2UgY29kZXMgb2YgdGhlIGFnZ3JlZ2F0aW9uIG1ldGhvZHMgYXJlIGF2YWlsYWJsZSBbaGVyZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPiBgciBmb250YXdlc29tZTo6ZmEoImdpdGh1YiIpYDwvc3Bhbj5dKGh0dHBzOi8vZ2l0aHViLmNvbS9oYXNzb3RoZWEvQWdncmVnYXRpb25NZXRob2RzKS4gVG8gcnVuIHRoZSBjb2RlcywgeW91IGNhbiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmBjbG9uZWA8L3NwYW4+IHRoZSByZXBvc2l0b3J5IGRpcmVjdGx5IG9yIHNpbXBseSBsb2FkIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmBSIHNjcmlwdGA8L3NwYW4+IHNvdXJjZSBmaWxlIGZyb20gdGhlIHJlcG9zaXRvcnkgdXNpbmcgW2RldnRvb2xzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGV2dG9vbHMvaW5kZXguaHRtbCkgcGFja2FnZSBpbiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwMjg3RDg7Ij4gKipSc3R1ZGlvKiogPC9zcGFuPiBhcyBmb2xsb3c6DQoNCjEuIEluc3RhbGwgW2RldnRvb2xzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGV2dG9vbHMvaW5kZXguaHRtbCkgcGFja2FnZSB1c2luZyBjb21tYW5kOiANCg0KICAgIGBpbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpYA0KDQoyLiBMb2FkaW5nIHRoZSBzb3VyY2UgY29kZXMgZnJvbSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPkdpdEh1YiBgciBmb250YXdlc29tZTo6ZmEoImdpdGh1YiIpYDwvc3Bhbj4gcmVwb3NpdG9yeSB1c2luZyBgc291cmNlX3VybGAgZnVuY3Rpb24gYnk6IA0KDQogICAgYGRldnRvb2xzOjpzb3VyY2VfdXJsKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vaGFzc290aGVhL0FnZ3JlZ2F0aW9uTWV0aG9kcy9tYWluL0tlcm5lbEFnZ1JlZy5SIilgDQoNCi0tLQ0KDQo+ICoqJiM5OTk4OyBOb3RlKio6IEFsbCBjb2RlcyBjb250YWluZWQgaW4gdGhpcyBgUm1hcmtkb3duYCBhcmUgYnVpbHQgd2l0aCByZWNlbnQgdmVyc2lvbiBvZiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmByIGZvbnRhd2Vzb21lOjpmYSgici1wcm9qZWN0IilgPC9zcGFuPiAodmVyc2lvbiAkPiQgNC4xLCBhdmFpbGFibGUgW2hlcmVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL2Jpbi93aW5kb3dzL2Jhc2UvKSkgYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjogIzAyODdEODsiPiAqKlJzdHVkaW8qKiA8L3NwYW4+ICh2ZXJzaW9uID4gYDIwMjIuMDIuMis0ODVgLCBhdmFpbGFibGUgW2hlcmVdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Byb2R1Y3RzL3JzdHVkaW8vZG93bmxvYWQvI2Rvd25sb2FkKSkuIE5vdGUgYWxzbyB0aGF0IHRoZSBjb2RlIGNodWNrcyBhcmUgPHNwYW4gc3R5bGU9ImNvbG9yOiAjRTYxODBBOyI+aGlkZGVuPC9zcGFuPiBieSBkZWZhdWx0Lg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQiPiAqKlRvIHNlZSB0aGUgY29kZXMsIHlvdSBjYW46KiogPC9zcGFuPg0KDQotIGNsaWNrIG9uIHRoZSB0b3AtcmlnaHQgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNTREMzE5IDsiPmBDb2RlYDwvc3Bhbj4gYnV0dG9uIG9mIHRoZSBwYWdlLCB0aGVuIGNob29zZSAqKlNob3cgQWxsIENvZGUqKiB0byBzaG93IGFsbCB0aGUgY29kZXMsIG9yIA0KLSBzaW1wbHkgY2xpY2sgb24gdGhlIHJpZ2h0LWNvcm5lciA8c3BhbiBzdHlsZT0iY29sb3I6ICM1NEQzMTkgOyI+YENvZGVgPC9zcGFuPiBidXR0b24gYXQgZWFjaCBzZWN0aW9uIHRvIHNob3cgdGhlIGNvZGVzIG9mIHRoYXQgc3BlY2lmaWMgc2VjdGlvbi4NCg0KLS0tDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPjx1PktGQyBwcm9jZWR1cmUgJiBpbXBvcnRhbnQgcGFja2FnZXMgPC91Pjwvc3Bhbj4NCj09PQ0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5LRkMgcHJvY2VkdXJlPC91Pjwvc3Bhbj4NCi0tLQ0KDQpLRkMgcHJvY2VkdXJlIGlzIGEgdGhyZWUtc3RlcCBtZXRob2RvbG9neSB3aGljaCBwdXRzIHRvZ2V0aGVyIGNsdXN0ZXJpbmcgYW5kIGNvbnNlbnN1YWwgYWdncmVnYXRpb24gbWV0aG9kcyB0byBjb25zdHJ1Y3QgcHJlZGljdGlvbnMgaW4gc3VwZXJ2aXNlZCBsZWFybmluZyBwcm9ibGVtcy4gVGhlIHRocmVlIHN0ZXBzIG9mIHRoZSBwcm9jZWR1cmUgYXJlOg0KDQotICoqU3RlcCAqSyogKio6ICRLJC1tZWFucyBjbHVzdGVyaW5nIGFsZ29yaXRobSBpcyBpbXBsZW1lbnRlZCBvbiB0aGUgaW5wdXQgZGF0YSB1c2luZyBzZXZlcmFsIG9wdGlvbnMgb2YgQnJlZ21hbiBkaXZlcmdlbmNlcyAkKHtcY2FsIEJ9X2opX3tqPTF9Xk0kICgkTSQgaXMgdGhlIG51bWJlciBvZiB0b3RhbCBkaXZlcmdlbmNlcyB1c2VkKSwgdGhlcmVmb3JlLCB0aGUgaW5wdXQgZGF0YSBpcyBwYXJ0aXRpb25lZCBpbnRvIG1hbnkgZGlmZmVyZW50IHN0cnVjdHVyZXMsIGFjY29yZGluZyB0byB0aGUgQnJlZ21hbiBkaXZlcmdlbmNlIHVzZWQuDQotICoqU3RlcCAqRiogKio6IEZvciBhIHBhcnRpdGlvbiBzdHJ1Y3R1cmUgZ2l2ZW4gYnkgYSBkaXZlcmdlbmNlICR7XGNhbCBCfV9qJCwgd2UgZml0IHNpbXBsZSBtb2RlbHMgKGxpbmVhciwgZm9yIGV4YW1wbGUpIG9uIGFsbCB0aGUgY2x1c3RlcnMgb2YgdGhlIG9idGFpbmVkIHBhcnRpdGlvbi4gVGhlbiwgdGhlIGNvbGxlY3Rpb24gJHtcY2FsIE19X2o9XHt7XGNhbCBNfV97aixrfVx9X3trPTF9XkskIG9mIHRoZXNlIGxvY2FsIG1vZGVscyBpcyBjYWxsZWQgKmNhbmRpZGF0ZSogbW9kZWwsIGNvcnJlc3BvbmRpbmcgdG8gdGhlIEJyZWdtYW4gZGl2ZXJnZW5jZSAke1xjYWwgQn1faiQuIEF0IHRoZSBlbmQgb2YgdGhpcyBzdGVwLCBzZXZlcmFsIGNhbmRpZGF0ZSBtb2RlbHMgYXJlIGNvbnN0cnVjdGVkLiANCi0gKipTdGVwICpDKiAqKjogVGhpcyBzdGVwIGFnZ3JlZ2F0ZXMgdGhlIG9idGFpbmVkIGNhbmRpZGF0ZSBtb2RlbHMgdXNpbmcgY29uc2Vuc3VhbCBhZ2dyZWdhdGlvbiBtZXRob2RzIHN0dWRpZWQgaW4gW0hhcyAoMjAyMSldKGh0dHBzOi8vaGFsLmFyY2hpdmVzLW91dmVydGVzLmZyL2hhbC0wMjg4NDMzM3Y1KSBvciBbRmlzY2hlciBhbmQgTW91Z2VvdCAoMjAxOSldKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAzNzgzNzU4MTgzMDIzNDkpLg0KDQoNCi0tLQ0KDQo+KipSZW1hcmsuMSoqOiANClRoZSBwcmVkaWN0aW9uIG9mIGFueSBvYnNlcnZhdGlvbiAkeCQgZ2l2ZW4gYnkgYSBjYW5kaWRhdGUgbW9kZWwgJHtcY2FsIE19X2okIGlzIGRvbmUgaW4gdHdvIHNpbXBsZSBzdGVwczoNCg0KMS4gJHgkIGlzIGNsYXNzaWZpZWQgaW50byBvbmUgb2YgdGhlIG9idGFpbmVkIGNsdXN0ZXJzIHVzaW5nIHRoZSBjb3JyZXNwb25kaW5nIGRpdmVyZ2VuY2UgJHtcY2FsIEJ9X2okLCBpLmUuLA0KICAkJHhcaW57XGNhbCBDfV97a14qfSBcTGVmdHJpZ2h0YXJyb3cge1xjYWwgQn1faihjX3trXip9LHgpPVxpbmZfezFcbGVxIGtcbGVxIEt9e1xjYWwgQn1faihjX2sseCkkJA0Kd2hlcmUgJFx7Y18xLC4uLixjX0tcfV97az0xfV5LJCBhcmUgdGhlIGNlbnRyb2lkcyBvZiB0aGUgY29ycmVzcG9uZGluZyBjbHVzdGVycyAkXHtDXzEsLi4uLENfS1x9JC4NCg0KMi4gVGhlIHByZWRpY3Rpb24gb2YgJHgkIGlzIGdpdmVuIGJ5IHRoZSBjb3JyZXNwb25kaW5nIGxvY2FsIG1vZGVsICR7XGNhbCBNfV97aixrXip9JCBkZWZpbmVkIG9uIGNsdXN0ZXIgJGteKiQsIGkuZS4sICR7XGNhbCBNfV9qKHgpPXtcY2FsIE19X3tqLGteKn0oeCkkLg0KDQotLS0NCg0KIVtUaGUgZmlndXJlIGFib3ZlIHJlcHJlc2VudHMgdGhlIHN1bW1hcnkgb2YgS0ZDIHByb2NlZHVyZV0oLi9rZmMucG5nKQ0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5JbXBvcnRhbnQgcGFja2FnZXM8L3U+PC9zcGFuPg0KLS0tDQoNCldlIHByZXBhcmUgYWxsIHRoZSBuZWNlc3NhcnkgdG9vbHMgZm9yIHRoaXMgYFJtYXJrZG93bmAuIGBwYWNtYW5gIHBhY2thZ2UgYWxsb3dzIHVzIHRvIGxvYWQgKGlmIGV4aXN0cykgb3IgaW5zdGFsbCAoaWYgZG9lcyBub3QgZXhpc3QpIGFueSBhdmFpbGFibGUgcGFja2FnZXMgZnJvbSBbVGhlIENvbXByZWhlbnNpdmUgUiBBcmNoaXZlIE5ldHdvcmsgKENSQU4pXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy8pIG9mIDxzcGFuIHN0eWxlPSJjb2xvcjogIzA5N0JDMSI+YHIgZm9udGF3ZXNvbWU6OmZhKCJyLXByb2plY3QiKWA8L3NwYW4+LiANCg0KDQpgYGB7cn0NCiMgQ2hlY2sgaWYgcGFja2FnZSAicGFjbWFuIiBpcyBhbHJlYWR5IGluc3RhbGxlZCANCg0KbG9va3VwX3BhY2thZ2VzIDwtIGluc3RhbGxlZC5wYWNrYWdlcygpWywxXQ0KaWYoISgicGFjbWFuIiAlaW4lIGxvb2t1cF9wYWNrYWdlcykpDQogIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQoNCg0KIyBUbyBiZSBpbnN0YWxsZWQgb3IgbG9hZGVkDQpwYWNtYW46OnBfbG9hZChtYWdyaXR0cikNCnBhY21hbjo6cF9sb2FkKHRpZHl2ZXJzZSkNCg0KIyMgcGFja2FnZSBmb3IgImdlbmVyYXRlTWFjaGluZXMiDQpwYWNtYW46OnBfbG9hZCh0cmVlKQ0KcGFjbWFuOjpwX2xvYWQoZ2xtbmV0KQ0KcGFjbWFuOjpwX2xvYWQocmFuZG9tRm9yZXN0KQ0KcGFjbWFuOjpwX2xvYWQoRk5OKQ0KcGFjbWFuOjpwX2xvYWQoeGdib29zdCkNCnBhY21hbjo6cF9sb2FkKGtlcmFzKQ0KcGFjbWFuOjpwX2xvYWQocHJhY21hKQ0KcGFjbWFuOjpwX2xvYWQobGF0ZXgyZXhwKQ0KcGFjbWFuOjpwX2xvYWQocGxvdGx5KQ0KcGFjbWFuOjpwX2xvYWQocGFyYWxsZWwpDQpwYWNtYW46OnBfbG9hZChmb3JlYWNoKQ0KcGFjbWFuOjpwX2xvYWQoZG9QYXJhbGxlbCkNCnJtKGxvb2t1cF9wYWNrYWdlcykNCmBgYA0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPjx1PkJyZWdtYW4gZGl2ZXJnZW5jZXMgKEJEKTwvdT48L3NwYW4+DQo9PT0NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+KipEZWZpbml0aW9uKio8L3NwYW4+IExldCAkXHBoaTpcbWF0aGNhbHtDfVxyaWdodGFycm93XG1hdGhiYntSfSQgYmUgYSBzdHJpY3RseSBjb252ZXggYW5kIGNvbnRpbnVvdXNseSBkaWZmZXJlbnRpYWJsZSBmdW5jdGlvbiBkZWZpbmVkIG9uIGEgbWVhc3VyYWJsZSBjb252ZXggc3Vic2V0ICRcbWF0aGNhbHtDfVxzdWJzZXRcbWF0aGJie1J9XmQkLiBMZXQgJGludChcbWF0aGNhbHtDfSkkIGRlbm90ZSBpdHMgcmVsYXRpdmUgaW50ZXJpb3IuIEEgQnJlZ21hbiBkaXZlcmdlbmNlIGluZGV4ZWQgYnkgJFxwaGkkIGlzIGEgZGlzc2ltaWxhcml0eSBtZWFzdXJlICRkX3tccGhpfTpcbWF0aGNhbHtDfVx0aW1lcyBpbnQoXG1hdGhjYWx7Q30pXHJpZ2h0YXJyb3dcbWF0aGJie1J9JCBkZWZpbmVkIGZvciBhbnkgcGFpciAkKHgseSlcaW4gXG1hdGhjYWx7Q31cdGltZXMgaW50KFxtYXRoY2Fse0N9KSQgYnksDQpcYmVnaW57ZXF1YXRpb259DQpcbGFiZWx7ZXE6MS4xMH0NCmRfe1xwaGl9KHgseSk9XHBoaSh4KS1ccGhpKHkpLVxsYW5nbGUgeC15LFxuYWJsYVxwaGkoeSlccmFuZ2xlIA0KXGVuZHtlcXVhdGlvbn0NCndoZXJlICRcbmFibGFccGhpKHkpJCBkZW5vdGVzIHRoZSBncmFkaWVudCBvZiAkXHBoaSQgY29tcHV0ZWQgYXQgYSBwb2ludCAkeVxpbiBpbnQoXG1hdGhjYWx7Q30pJC4gQSBCcmVnbWFuIGRpdmVyZ2VuY2UgaXMgbm90IG5lY2Vzc2FyaWx5IGEgbWV0cmljIGFzIGl0IG1heSBub3QgYmUgc3ltbWV0cmljIGFuZCB0aGUgdHJpYW5ndWxhciBpbmVxdWFsaXR5IG1pZ2h0IG5vdCBiZSBzYXRpc2ZpZWQuDQoNClRoaXMgc2VjdGlvbiBkZWZpbmVzIGFsbCB0aGUgQnJlZ21hbiBkaXZlcmdlbmNlcyB1c2VkLiBUaGUgbGlzdCBvZiBhbGwgdGhlIEJyZWdtYW4gZGl2ZXJnZW5jZXMgaXMgZ2l2ZW4gaW4gdGhlIHRhYmxlIGJlbG93Og0KDQotLS0tDQoNCk5hbWUgICAgICAgICAgICAgICAgICAgICAgICAkXHBoaSQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJGRfe1xwaGl9JCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAkXGNhbCBDJA0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAtLS0tLS0tLS0tLQ0KRXVjbGlkZWFuICAgICAgICAgICAgICAgICAke1x8eFx8XzJeMn09XHN1bV97aT0xfV5keF9pXjIkICAgICAgICAgICAgICAgICAgICRcfHgteVx8XzJeMiQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAkXG1hdGhiYntSfV5kJA0KR2VuZXJhbCBLdWxsYmFjay1MZWlibGVyICAkXHN1bV97aT0xfV5kIHhfaVxsbiggeF9pKSQgICAgICAgICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmQoIHhfaVxsbihcZnJhY3sgeF9pfXt5X2l9KS0oeF9pLXlfaSkpJCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAkKDAsK1xpbmZ0eSleZCQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpMb2dpc3RpYyAgICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmQoeF9pXGxuKCB4X2kpKygxLSB4X2kpXGxuKDEtIHhfaSkpJCAgJFxzdW1fe2k9MX1eZFxCaWcoIHhfaVxsbihcZnJhY3t4X2l9e3lfaX0pKygxLSB4X2kpXGxuKFxmcmFjezEtIHhfaX17MS15X2l9KVxCaWcpJCAgICQoMCwxKV5kJA0KSXRha3VyYS1TYWl0byAgICAgICAgICAgICAkLVxzdW1fe2k9MX1eZFxsbiggeF9pKSQgICAgICAgICAgICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmRcQmlnKFxmcmFjeyB4X2l9e3lfaX0tXGxuKFxmcmFjeyB4X2l9e3lfaX0pLTFcQmlnKSQgICAgICAgICAgICAgICAgICAgICQoMCwrXGluZnR5KV5kJA0KRXhwb25lbnRpYWwgICAgICAgICAgICAgICAkXHN1bV97aT0xfV5kZV57eF9pfSQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmQoZV57eF9pfS1lXnt5X2l9LWVee3lfaX0oeF9pLXlfaSkpJCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICRcbWF0aGJie1J9XmQkDQpQb2x5bm9taWFsICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmR8eHxecCxwPjIkICAgICAgICAgICAgICAgICAgICAgICAgICAgJFxzdW1fe2s9MX1eZCh8eF9rfF5wLXx5X2t8XnAtXHRleHR7c2lnbn0oeV9rKV5wcCh4X2steV9rKXlfa157cC0xfSkkICAgICAgICAgICAgICRcbWF0aGJie1J9XmQkDQoNCi0tLS0NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+TG9vay11cCBsaXN0IG9mIEJyZWdtYW4gZGl2ZXJnZW5jZXM8L3U+PC9zcGFuPg0KLS0tLQ0KDQpUaGUgY29kZXMgYmVsb3cgcHJvdmlkZSBhIGxvb2stdXAgbGlzdCBvZiBhbGwgdGhlIEJEcyBkZWZpbmVkIGluIHRoZSB0YWJsZSBhYm92ZS4NCg0KYGBge3J9DQpldWNsaWREaXYgPC0gZnVuY3Rpb24oWC4sIHkuLCBkZWcgPSBOVUxMKXsNCiAgICByZXMgPC0gc3dlZXAoWC4sIDIsIHkuKQ0KICAgIHJldHVybihyb3dTdW1zKHJlc14yKSkNCn0NCmdrbERpdiA8LSBmdW5jdGlvbihYLiwgeS4sIGRlZyA9IE5VTEwpew0KICByZXMgPC0gYygiLyIsICItIikgJT4lDQogICAgbWFwKC5mID0gfiBzd2VlcChYLiwgMiwgeS4sIEZVTiA9IC54KSkNCiAgcmV0dXJuKHJvd1N1bXMoWC4qbG9nKHJlc1tbMV1dKSAtIHJlc1tbMl1dKSkNCn0NCmxvZ0RpdiA8LSBmdW5jdGlvbihYLiwgeS4sIGRlZyA9IE5VTEwpew0KICAgIHJlcyA8LSAgbWFwMigueCA9IGxpc3QoWC4sIDEtWC4pLA0KICAgICAgICAgICAgICAgICAueSA9IGxpc3QoeS4sIDEteS4pLA0KICAgICAgICAgICAgICAgICAuZiA9IH4gc3dlZXAoLngsIDIsIC55LCBGVU4gPSAiLyIpKQ0KICAgIHJldHVybihyb3dTdW1zKFguKmxvZyhyZXNbWzFdXSkrKDEtWC4pKmxvZyhyZXNbWzJdXSkpKQ0KfQ0KaXRhRGl2IDwtIGZ1bmN0aW9uKFguLCB5LiwgZGVnID0gTlVMTCl7DQogICAgcmVzIDwtIHN3ZWVwKFguLCAyLCB5LiwgRlVOID0gIi8iKQ0KICAgIHJldHVybihyb3dTdW1zKHJlcy1sb2cocmVzKSAtIDEpKQ0KfQ0KZXhwRGl2IDwtIGZ1bmN0aW9uKFguLCB5LiwgZGVnID0gTlVMTCl7DQogICAgZXhwX3kgPC0gZXhwKHkuKQ0KICAgIHJlcyA8LSBzd2VlcCgxK1guLCAyLCB5LikgJT4lDQogICAgICBzd2VlcCgyLCBleHBfeSwgRlVOID0gIioiKQ0KICAgIHJldHVybihyb3dTdW1zKGV4cChYLiktcmVzKSkNCn0NCnBvbHlEaXYgPC0gZnVuY3Rpb24oWC4sIHkuLCBkZWcgPSAzKXsNCiAgICBTIDwtIG1hcDIoLnggPSBsaXN0KFguLCBYLl5kZWcpLA0KICAgICAgICAgICAgICAueSA9IGxpc3QoeS4sIHkuXmRlZyksDQogICAgICAgICAgICAgIC5mID0gfiBzd2VlcCgueCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBNQVJHSU4gPSAyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIFNUQVRTID0gLnksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBGVU4gPSAiLSIpKQ0KICAgIGlmKGRlZyAlJSAyID09IDApew0KICAgICAgVGVtIDwtIHN3ZWVwKFNbWzFdXSwgMiwgeS5eKGRlZy0xKSwgRlVOID0gIioiKQ0KICAgICAgcmVzIDwtIHJvd1N1bXMoU1tbMl1dIC0gZGVnICogVGVtKQ0KICAgIH0NCiAgICBlbHNlew0KICAgICAgVGVtIDwtIHN3ZWVwKFNbWzFdXSwgMiwgc2lnbih5LikgKiB5Ll4oZGVnLTEpLCBGVU4gPSAiKiIpDQogICAgICByZXMgPC0gcm93U3VtcyhTW1syXV0gLSBkZWcgKiBUZW0pDQogICAgfQ0KICAgIHJldHVybihyZXMpDQp9DQpsb29rdXBfZGl2IDwtIGxpc3QoDQogIGV1Y2xpZGVhbiA9IGV1Y2xpZERpdiwNCiAgZ2tsID0gZ2tsRGl2LA0KICBsb2dpc3RpYyA9IGxvZ0RpdiwNCiAgaXRha3VyYSA9IGl0YURpdiwNCiAgZXhwb25lbnRpYWwgPSBleHBEaXYsDQogIHBvbHlub21pYWwgPSBwb2x5RGl2DQopDQpgYGANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5GdW5jdGlvbjwvdT48L3NwYW4+IDogYEJyZWdtYW5EaXZgDQotLS0tDQoNClRoaXMgZnVuY3Rpb24gY29tcHV0ZXMgdGhlIG1hdHJpeCBvZiBCRHMgYmV0d2VlbiB0d28gc2V0cyBvZiBkYXRhIHBvaW50cy4gRWFjaCBzZXQgb2YgZGF0YSBwb2ludHMgc2hvdWxkIGJlIHN0b3JlZCBhcyBtYXRyaWNlcywgZGF0YSBmcmFtZSwgb3IgdGliYmxlIG9iamVjdCB3aGVyZSBlYWNoIHJvdyByZXByZXNlbnRzIGFuIGluZGl2aWR1YWwgZGF0YSBwb2ludC4NCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGBYLmAsIGBDLmAgOiBUaGUgZGF0YSBtYXRyaWNlcywgdGliYmxlcyBhbmQgZGF0YSBmcmFtZXMsIGNvbnRhaW5pbmcgdGhlIGRhdGEgcG9pbnRzIGZvciB3aGljaCB0aGUgQnJlZ21hbiBkaXZlcmdlbmNlcyBiZXR3ZWVuIHRoZW0gYXJlIHRvIGJlIGNvbXB1dGVkLg0KICAgIC0gYGRpdmAgOiB0aGUgdHlwZSBvZiBkaXZlcmdlbmNlIHRvIGJlIHVzZWQuIEl0IHNob3VsZCBiZSBhIHN1YnNldCBvZiBgeyJldWNsaWRlYW4iLCAiZ2tsIiwgImxvZ2lzdGljIiwgIml0YWt1cmEiLCAiZXhwb25lbnRpYWwiLCAicG9seW5vbWlhbCJ9YC4NCiAgICAtIGBkZWdgIDogdGhlIGRlZ3JlZSBvZiBwb2x5bm9taWFsIEJEIChpZiBvbmUgaXMgdXNlZCkuDQotICoqVmFsdWUqKjogDQogICAgDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgKnRpYmJsZSogb2JqZWN0ICREPShkX3tpLGp9KSQgd2hlcmUgJGRfe2ksan0kIGlzIHRoZSBCcmVnbWFuIGRpdmVyZ2VuY2UgYmV0d2VlbiByb3cgJGkkIG9mIGBYLmAgYW5kIHJvdyAkaiQgb2YgYEMuYC4NCg0KDQpgYGB7cn0NCkJyZWdtYW5EaXYgPC0gZnVuY3Rpb24oWC4sIA0KICAgICAgICAgICAgICAgICAgICAgICBDLiwgDQogICAgICAgICAgICAgICAgICAgICAgIGRpdiA9IGMoImV1Y2xpZGVhbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJna2wiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibG9naXN0aWMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaXRha3VyYSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJleHBvbmVudGlhbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJwb2x5bm9taWFsIiksDQogICAgICAgICAgICAgICAgICAgICAgIGRlZyA9IDMpew0KICBkaXYgPC0gbWF0Y2guYXJnKGRpdikNCiAgZF9jIDwtIGRpbShDLikNCiAgaWYoaXMubnVsbChkX2MpKXsNCiAgICBDIDwtIG1hdHJpeChDLiwgbnJvdyA9IDEsIGJ5cm93ID0gVFJVRSkNCiAgfSBlbHNlew0KICAgIEMgPC0gYXMubWF0cml4KEMuKQ0KICB9DQogIGlmKGlzLm51bGwoZGltKFguKSkpew0KICAgIFggPC0gbWF0cml4KFguLCBucm93ID0gMSwgYnlyb3cgPSBUUlVFKQ0KICB9IGVsc2V7DQogICAgWCA8LSBhcy5tYXRyaXgoWC4pDQogIH0NCiAgZGlzIDwtICBtYXBfZGZjKC54ID0gMTpkaW0oQylbMV0sDQogICAgICAgICAgICAgICAgICAuZiA9IH4gdGliYmxlKCd7ey54fX0nIDo9IGxvb2t1cF9kaXZbW2Rpdl1dKFgsIENbLngsXSwgZGVnID0gZGVnKSkpDQogIHJldHVybihkaXMpDQp9DQpgYGANCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+U3RlcCAkSyQ6ICRLJC1tZWFucyB3aXRoIEJyZWdtYW4gZGl2ZXJnZW5jZXM8L3U+PC9zcGFuPg0KPT09DQoNClRoaXMgc2VjdGlvbiBpbXBsZW1lbnRzICRLJC1tZWFucyBhbGdvcml0aG0gdXNpbmcgQnJlZ21hbiBkaXZlcmdlbmNlcyB3aGljaCBjb3JyZXNwb25kcyB0byB0aGUgc3RlcCAkSyQgb2YgS0ZDIHByb2NlZHVyZS4NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPiA6IGBmaW5kQ2xvc2VzdENlbnRyb2lkYCBhbmQgYG5ld0NlbnRyb2lkc2ANCi0tLS0NCg0KVGhlc2UgdHdvIGZ1bmN0aW9ucyBwZXJmb3JtIHRoZSBtYWluIHN0ZXBzIG9mICRLJC1tZWFucyBhbGdvcml0aG0uIGBmaW5kQ2xvc2VzdENlbnRyb2lkYCBmdW5jdGlvbiBhc3NpZ25zIGFueSBkYXRhIHBvaW50cyB0byBzb21lIGNsdXN0ZXIgYWNjb3JkaW5nIHRvIHRoZSBuZWFyZXN0IGNlbnRyb2lkIGFtb25nIGFsbCB0aGUgY2VudHJvaWRzLCBhbmQgYG5ld0NlbnRyb2lkc2AgZnVuY3Rpb24gY29tcHV0ZSB0aGUgbmV3IGNlbnRyb2lkcyBvZiB0aGUgcGFydGl0aW9uIGdpdmVuIHRoZSBjbHVzdGVyIGxhYmVscyBvZiBhbGwgZGF0YSBwb2ludHMuDQoNCi0gKipBcmd1bWVudCoqOg0KDQogICAgLSBgeC5gIDogVGhlIGRhdGEgbWF0cmljZXMsIHRpYmJsZXMgYW5kIGRhdGEgZnJhbWVzLCBjb250YWluaW5nIHRoZSBkYXRhIHBvaW50cyB0byBiZSBhc3NpZ25lZCB0byBzb21lIGNsdXN0ZXIuDQogICAgLSBgY2VudHJvaWRzYCA6IFRoZSBtYXRyaXggb3IgZGF0YSBmcmFtZSBjb250YWluaW5nIHRoZSBjZW50cm9pZHMgKGJ5IHJvdykuDQogICAgLSBgZGl2YCA6IHRoZSB0eXBlIG9mIGRpdmVyZ2VuY2UgdG8gYmUgdXNlZC4NCiAgICAtIGBkZWdgIDogdGhlIGRlZ3JlZSBvZiBwb2x5bm9taWFsIEJEIChpZiBvbmUgaXMgdXNlZCkuDQogICAgDQotICoqVmFsdWUqKjogDQoNCiAgICBUaGUgZWFjaCBvZiB0aGUgdHdvIGZ1bmN0aW9ucyByZXR1cm5zIGFyZ3VtZW50cyBmb3Igb25lIGFub3RoZXI6DQogICAgDQogICAgLSBgZmluZENsb3Nlc3RDZW50cm9pZGAgcmV0dXJucyBhIHZlY3RvciBvZiBzaXplIGVxdWFscyB0byB0aGUgbnVtYmVyIG9mIHJvd3Mgb2YgZGF0YSBtYXRyaXggYHguYCwgY29udGFpbmluZyB0aGUgY2x1c3RlciBsYWJlbHMgb2YgdGhlIGRhdGEgcG9pbnRzLg0KICAgIC0gYG5ld0NlbnRyb2lkc2AgcmV0dXJucyB0aGUgbWF0cml4IG9mIGNlbnRyb2lkcy4NCg0KYGBge3J9DQpmaW5kQ2xvc2VzdENlbnRyb2lkIDwtIGZ1bmN0aW9uKHguLCBjZW50cm9pZHMuLCBkaXYsIGRlZyA9IDMpew0KICBkaXN0IDwtIEJyZWdtYW5EaXYoeC4sIGNlbnRyb2lkcy4sIGRpdiwgZGVnKQ0KICBjbHVzdCA8LSAxOm5yb3coeC4pICU+JQ0KICAgIG1hcF9pbnQoLmYgPSB+IHdoaWNoLm1pbihkaXN0Wy54LF0pKQ0KICByZXR1cm4oY2x1c3QpDQp9DQpuZXdDZW50cm9pZHMgPC0gZnVuY3Rpb24oeC4sIGNsdXN0ZXJzLil7DQogIGNlbnRyb2lkcyA8LSB1bmlxdWUoY2x1c3RlcnMuKSAlPiUNCiAgICBtYXBfZGZyKC5mID0gfiBjb2xNZWFucyh4LltjbHVzdGVycy4gPT0gLngsIF0pKQ0KICByZXR1cm4oY2VudHJvaWRzKQ0KfQ0KYGBgDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZ1bmN0aW9uPC91Pjwvc3Bhbj4gOiBga21lYW5zQkRgDQotLS0tDQoNClRoaXMgZnVuY3Rpb24gcGVyZm9ybXMgJEskLW1lYW5zIGFsZ29yaXRobSB3aXRoIEJEcy4gDQoNCi0gKipBcmd1bWVudCoqOg0KDQogICAgLSBgdHJhaW5faW5wdXRgIDogVGhlIGRhdGEgbWF0cmljZXMsIHRpYmJsZXMgYW5kIGRhdGEgZnJhbWVzLCBjb250YWluaW5nIHRoZSBkYXRhIHBvaW50cy4NCiAgICAtIGBLYCA6IFRoZSBudW1iZXIgb2YgY2x1c3RlcnMuDQogICAgLSBgbl9zdGFydGAgOiB0aGUgbnVtYmVyIG9mIHRpbWVzIHRvIHBlcmZvcm0gdGhlIGFsZ29yaXRobSwgYW5kIHRoZSBiZXN0IG9uZSBhbW9uZyB0aGVtIGlzIGNob3NlbiB0byBiZSB0aGUgZmluYWwgcmVzdWx0LiBUaGlzIGlzIGRvbmUgdG8gYXZvaWQgbG9jYWwgb3B0aW1hbCBzb2x1dGlvbnMuIEJ5IGRlZmF1bHQsIGBuX3N0YXJ0ID0gNWAuDQogICAgLSBgbWF4SXRlcmAgOiB0aGUgbWF4aW11bSBudW1iZXIgb2YgaXRlcmF0aW9ucyBpbiBjYXNlIHRoZSBhbGdvcml0aG0gZG9lcyBub3QgY29udmVyZ2UuIEJ5IGRlZmF1bHQsIGBtYXhJdGVyID0gNTAwYC4NCiAgICAtIGBkZWdgIDogdGhlIGRlZ3JlZSBvZiBwb2x5bm9taWFsIEJEIChpZiBvbmUgaXMgdXNlZCkuDQogICAgLSBgc2NhbGVfaW5wdXRgIDogbG9naWNhbCB2YWx1ZSBjb250cm9sbGluZyB3aGV0aGVyIHRvIHNjYWxlIHRoZSBpbnB1dCB0byBiZSBpbiAkKDAsMSkkIG9yIG5vdC4gQnkgZGVmYXVsdCwgYHNjYWxlX2lucHV0ID0gRkFMU0VgLg0KICAgIC0gYGRpdmAgOiB0aGUgdHlwZSBvZiBkaXZlcmdlbmNlIHRvIGJlIHVzZWQuIEJ5IGRlZmF1bHQsIGBkaXYgPSAiZXVjbGlkZWFuImAgYW5kIHRoZSB1c3VhbCAkSyQtbWVhbnMgYWxnb3JpdGhtIGlzIHBlcmZvcm1lZC4NCiAgICAtIGBzcGxpdHNgIDogcmVhbCBudW1iZXIgYmV0d2VlbiAkMCQgYW5kICQxJCBzcGVjaWZ5aW5nIHRoZSBwcm9wb3J0aW9uIG9mIHRyYWluaW5nIGRhdGEgdG8gYmUgdXNlZCB0byBwZXJmb3JtICRLJC1tZWFucyBhbGdvcml0aG0uIFRoZSByZW1haW5pbmcgcGFydCB3aXRoIGJlIHVzZWQgZm9yIHRoZSBhZ2dyZWdhdGlvbi4gQnkgZGVmYXVsdCwgYHNwbGl0cyA9IDFgIGFuZCBhbGwgdGhlIGlucHV0IGRhdGEgYXJlIHVzZWQuDQogICAgLSBgZXBzaWxvbmAgOiB0aGUgc3RvcHBpbmcgdGhyZXNob2xkIGNyaXRlcmlvbiB0byBzdG9wIHRoZSBhbGdvcml0aG0uIEJ5IGRlZmF1bHQsIGBlcHNpbG9uID0gMWUtMTBgLg0KICAgIC0gYGNlbnRlcl9gLCBgc2NhbGVfYCA6IHRoZSBjZW50ZXIgYW5kIHNjYWxlIHRvIGJlIHVzZWQgdG8gc2NhbGUgdGhlIGlucHV0IGRhdGEuIEJ5IGRlZmF1bHQsIHRoZXkgYXJlIGBOVUxMYC4NCiAgICAtIGBpZF9zaHVmZmxlYCA6IGEgbG9naWNhbCB2ZWN0b3Igc3BlY2lmeWluZyB3aGljaCBwYXJ0IG9mIHRoZSB0cmFpbmluZyBkYXRhIHdpbGwgYmUgc2VsZWN0ZWQgdG8gcGVyZm9ybSB0aGUgYWxnb3JpdGhtLiBUaGlzIGlzIGltcG9ydGFudCB3aGVuIHdlIHdhbnQgdG8gcGVyZm9ybSB0aGUgYWxnb3JpdGhtIG9uIHRoZSBzYW1lIHNldCBvZiBkYXRhIHBvaW50cyBidXQgd2l0aCBkaWZmZXJlbnQgQkRzLiANCiAgICANCi0gKipWYWx1ZSoqOiANCg0KICAgIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhIGxpc3Qgb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzOg0KICAgIC0gYGNlbnRyb2lkc2AgOiB0aGUgbWF0cml4IG9mIHRoZSBvYnRhaW5lZCBjZW50cm9pZHMuDQogICAgLSBgY2x1c3RlcnNgIDogYSB2ZWN0b3Igb2YgY2x1c3RlciBsYWJlbCBvZiB0aGUgZGF0YSBwb2ludHMuDQogICAgLSBgdHJhaW5fZGF0YWAgOiBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgaW1wb3J0YW50IG9iamVjdHMNCiAgICAgICAgLSBgWF90cmFpbmAgOiB0aGUgdHJhaW5pbmcgZGF0YSB1c2VkIGZvciB0aGUgYWxnb3JpdGhtLg0KICAgICAgICAtIGBYX3JlbWFpbmAgOiB0aGUgcmVtYWluaW5nIHBhcnQgb2YgdGhlIGlucHV0IGRhdGEgdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uLg0KICAgICAgICAtIGBpZF9yZW1haW5gIDogdGhlIGxvZ2ljYWwgdmVjdG9yIHNwZWNpZnlpbmcgdGhlIHJlbWFpbmluZyBwYXJ0IChgWF9yZW1haW5gKSBvZiB0aGUgaW5wdXQgZGF0YS4NCiAgICAtIGBwYXJhbWV0ZXJzYCA6IHRoZSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCiAgICAgICAgLSBgZGl2YCA6IGRpdmVyZ2VuY2UgdXNlZC4NCiAgICAgICAgLSBgZGVnYCA6IHRoZSBkZWdyZWUgb2YgcG9seW5vbWlhbCBCRCAoaWYgb25lIGlzIHVzZWQpLg0KICAgICAgICAtIGBjZW50ZXJfYCwgYHNjYWxlX2AgOiB0aGUgY2VudGVyIGFuZCBzY2FsZSB1c2VkIHRvIHNjYWxlIHRoZSBpbnB1dCBkYXRhLg0KICAgIC0gYHJ1bm5pbmdfdGltZWA6IHRoZSBjb21wdXRhdGlvbmFsIHRpbWUgb2YgdGhlIGFsZ29yaXRobS4NCg0KYGBge3J9DQprbWVhbnNCRCA8LSBmdW5jdGlvbih0cmFpbl9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgIEssDQogICAgICAgICAgICAgICAgICAgICBuX3N0YXJ0ID0gNSwNCiAgICAgICAgICAgICAgICAgICAgIG1heEl0ZXIgPSA1MDAsDQogICAgICAgICAgICAgICAgICAgICBkZWcgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgIGRpdiA9ICJldWNsaWRlYW4iLA0KICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gMSwNCiAgICAgICAgICAgICAgICAgICAgIGVwc2lsb24gPSAxZS0xMCwNCiAgICAgICAgICAgICAgICAgICAgIGNlbnRlcl8gPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgc2NhbGVfID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBOVUxMKXsNCiAgc3RhcnRfdGltZSA8LSBTeXMudGltZSgpDQogICMgRGlzdG9ydGlvbiBmdW5jdGlvbg0KICBYIDwtIGFzLm1hdHJpeCh0cmFpbl9pbnB1dCkNCiAgTiA8LSBkaW0oWCkNCiAgaWYoc2NhbGVfaW5wdXQpew0KICAgIGlmKCEoaXMubnVsbChjZW50ZXJfKSAmIGlzLm51bGwoc2NhbGVfKSkpew0KICAgICAgaWYobGVuZ3RoKGNlbnRlcl8pID09IDEpew0KICAgICAgICBjZW50ZXJfIDwtIHJlcChjZW50ZXJfLCBOWzJdKQ0KICAgICAgfQ0KICAgICAgaWYobGVuZ3RoKHNjYWxlXykgPT0gMSl7DQogICAgICAgIHNjYWxlXyA8LSByZXAoc2NhbGVfLCBOWzJdKQ0KICAgICAgfQ0KICAgIH0gZWxzZXsNCiAgICAgIG1pbl8gPC0gYXBwbHkoWCwgMiwgRlVOID0gbWluKQ0KICAgICAgY18gPC0gYWJzKGNvbE1lYW5zKFgpLzUpDQogICAgICBjZW50ZXJfIDwtIG1pbl8gLSBjXw0KICAgICAgc2NhbGVfIDwtIGFwcGx5KFgsIDIsIEZVTiA9IG1heCkgLSBjZW50ZXJfICsgMQ0KICAgIH0NCiAgICBYIDwtIHNjYWxlKFgsIGNlbnRlciA9IGNlbnRlcl8sIHNjYWxlID0gc2NhbGVfKQ0KICB9DQogIGlmKGlzLm51bGwoaWRfc2h1ZmZsZSkpew0KICAgIHRyYWluX2lkIDwtIHJlcChUUlVFLCBOWzFdKQ0KICAgIGlmKHNwbGl0cyA8IDEpew0KICAgICAgdHJhaW5faWRbc2FtcGxlKE5bMV0sIGZsb29yKE5bMV0qKDEtc3BsaXRzKSkpXSA8LSBGQUxTRQ0KICAgIH0NCiAgfSBlbHNlew0KICAgIHRyYWluX2lkIDwtIGlkX3NodWZmbGUNCiAgfQ0KICBYX3RyYWluMSA8LSBYW3RyYWluX2lkLF0NCiAgWF90cmFpbjIgPC0gWFshdHJhaW5faWQsXQ0KICBtdSA8LSBhcy5tYXRyaXgoY29sTWVhbnMoWF90cmFpbjEpKQ0KICBkaXN0b3J0aW9uIDwtIGZ1bmN0aW9uKGNsdXMpew0KICAgIGNlbnQgPC0gbmV3Q2VudHJvaWRzKFhfdHJhaW4xLCBjbHVzKQ0KICAgIHZhcl93aXRoaW4gPC0gMTpLICU+JQ0KICAgICAgbWFwKC5mID0gfiBCcmVnbWFuRGl2KFhfdHJhaW4xW2NsdXMgPT0gLngsXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VudFsueCxdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXYsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZykpICU+JQ0KICAgICAgbWFwKC5mID0gc3VtKSAlPiUNCiAgICAgIFJlZHVjZSgiKyIsIC4pDQogICAgcmV0dXJuKHZhcl93aXRoaW4pDQogIH0NCiAgIyBLbWVhbnMgYWxnb3JpdGhtDQogIGttZWFuc1dpdGhCRCA8LSBmdW5jdGlvbih4Liwgay4sIG1heGl0ZXIuLCBlcHMuKSB7DQogICAgbi4gPC0gbnJvdyh4LikNCiAgICAjIGluaXRpYWxpemF0aW9uDQogICAgaW5pdCA8LSBzYW1wbGUobi4sIGsuKQ0KICAgIGNlbnRyb2lkc19vbGQgPC0geC5baW5pdCxdDQogICAgaSA8LSAwDQogICAgd2hpbGUoaSA8IG1heEl0ZXIpew0KICAgICAgIyBBc3NpZ25tZW50IHN0ZXANCiAgICAgIGNsdXN0ZXJzIDwtIGZpbmRDbG9zZXN0Q2VudHJvaWQoeC4sIGNlbnRyb2lkc19vbGQsIGRpdiwgZGVnKQ0KICAgICAgIyBSZWNvbXB1dGUgY2VudHJvaWRzDQogICAgICBjZW50cm9pZHNfbmV3IDwtIG5ld0NlbnRyb2lkcyh4LiwgY2x1c3RlcnMpDQogICAgICBpZiAoKHN1bShpcy5uYShjZW50cm9pZHNfbmV3KSkgPiAwKSB8DQogICAgICAgICAgKG5yb3coY2VudHJvaWRzX25ldykgIT0gay4pKSB7DQogICAgICAgIGluaXQgPC0gc2FtcGxlKG4uLCBrLikNCiAgICAgICAgY2VudHJvaWRzX29sZCA8LSB4Lltpbml0LF0NCiAgICAgICAgd2FybmluZygiTkEgcHJvZHVjZWQgLT4gcmVpbml0aWFsaXplIGNlbnRyb2lkcy4uLiEiKQ0KICAgICAgfQ0KICAgICAgZWxzZXsNCiAgICAgICAgaWYoc3VtKGFicyhjZW50cm9pZHNfbmV3IC0gY2VudHJvaWRzX29sZCkpID4gZXBzLil7DQogICAgICAgICAgY2VudHJvaWRzX29sZCA8LSBjZW50cm9pZHNfbmV3DQogICAgICAgIH0gZWxzZXsNCiAgICAgICAgICBicmVhaw0KICAgICAgICB9DQogICAgICB9DQogICAgICBpIDwtIGkgKyAxDQogICAgfQ0KICAgIHJldHVybihjbHVzdGVycykNCiAgfQ0KICByZXN1bHRzIDwtIDE6bl9zdGFydCAlPiUgDQogICAgbWFwX2RmYyguZiA9IH4gdGliYmxlKCJ7ey54fX0iIDo9IGttZWFuc1dpdGhCRChYX3RyYWluMSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBLLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4SXRlciwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlcHNpbG9uKSkpDQogIG9wdF9pZCA8LSAxOm5fc3RhcnQgJT4lDQogICAgbWFwX2RibCguZiA9IH4gZGlzdG9ydGlvbihyZXN1bHRzW1sueF1dKSkgJT4lDQogICAgd2hpY2gubWluDQogIGNsdXN0ZXIgPC0gY2x1c3RlcnMgPC0gcmVzdWx0c1tbb3B0X2lkXV0NCiAgaiA8LSAxDQogIElEIDwtIHVuaXF1ZShjbHVzdGVyKQ0KICBmb3IgKGkgaW4gSUQpIHsNCiAgICBjbHVzdGVyc1tjbHVzdGVyID09IGldID0gag0KICAgIGogPSAgaiArIDENCiAgfQ0KICBjZW50cm9pZHMgPSBuZXdDZW50cm9pZHMoWF90cmFpbjEsIGNsdXN0ZXJzKQ0KICB0aW1lX3Rha2VuIDwtIFN5cy50aW1lKCkgLSBzdGFydF90aW1lDQogIHJldHVybigNCiAgICBsaXN0KA0KICAgICAgY2VudHJvaWRzID0gY2VudHJvaWRzLA0KICAgICAgY2x1c3RlcnMgPSBjbHVzdGVycywNCiAgICAgIHRyYWluX2RhdGEgPSBsaXN0KFhfdHJhaW4gPSBYX3RyYWluMSwNCiAgICAgICAgICAgICAgICAgICAgICAgIFhfcmVtYWluID0gWF90cmFpbjIsDQogICAgICAgICAgICAgICAgICAgICAgICBpZF9yZW1haW4gPSAhdHJhaW5faWQpLA0KICAgICAgcGFyYW1ldGVycyA9IGxpc3QoZGl2ID0gZGl2LA0KICAgICAgICAgICAgICAgICAgICAgICAgZGVnID0gZGVnLA0KICAgICAgICAgICAgICAgICAgICAgICAgY2VudGVyXyA9IGNlbnRlcl8sDQogICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV8gPSBzY2FsZV8pLA0KICAgICAgcnVubmluZ190aW1lID0gdGltZV90YWtlbg0KICAgICkNCiAgKQ0KfQ0KYGBgDQoNCi0tLS0NCg0KPiAqKkV4YW1wbGUuMSoqOiBXZSBwZXJmb3JtICRLJC1tZWFucyBhbGdvcml0aG0gd2l0aCBgImdrbCJgIEJEIG9uIFtBYmFsb25lXShodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvbWFjaGluZS1sZWFybmluZy1kYXRhYmFzZXMvYWJhbG9uZSkgZGF0YXNldC4NCg0KLS0tLQ0KDQpgYGB7cn0NCnBhY21hbjo6cF9sb2FkKHJlYWRyKQ0KY29sbmFtZSA8LSBjKCJUeXBlIiwgIkxvbmdlc3RTaGVsbCIsICJEaWFtZXRlciIsICJIZWlnaHQiLCAiV2hvbGVXZWlnaHQiLCAiU2h1Y2tlZFdlaWdodCIsICJWaXNjZXJhV2VpZ2h0IiwgIlNoZWxsV2VpZ2h0IiwgIlJpbmdzIikNCmRmIDwtIHJlYWRyOjpyZWFkX2RlbGltKCJodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvbWFjaGluZS1sZWFybmluZy1kYXRhYmFzZXMvYWJhbG9uZS9hYmFsb25lLmRhdGEiLCBjb2xfbmFtZXMgPSBjb2xuYW1lLCBkZWxpbSA9ICIsIiwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkNCm4gPC0gbnJvdyhkZikNCnRyYWluIDwtIGxvZ2ljYWwobikNCnRyYWluW3NhbXBsZShuLCAgZmxvb3IobiowLjgpKV0gPC0gVFJVRQ0KY2wgPC0gZGZbdHJhaW4sMjoobmNvbChkZiktMSldICU+JQ0KICBrbWVhbnNCRChLID0gMywgZGl2ID0gImdrbCIsIHNwbGl0cyA9IDAuNSwgc2NhbGVfaW5wdXQgPSBUUlVFKQ0KdGFibGUoY2wkY2x1c3RlcnMpDQpgYGANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij48dT5TdGVwICRGJDogRml0dGluZyBwcmVkaWN0aXZlIG1vZGVsczwvdT48L3NwYW4+DQo9PT0NCg0KVGhpcyBzZWN0aW9uIGJ1aWxkcyBnbG9iYWwgbW9kZWxzIGJ5IGZpdHRpbmcgbG9jYWwgbW9kZWwgb24gZWFjaCBnaXZlbiBjbHVzdGVyIG9mIHRoZSBvYnRhaW5lZCBwYXJ0aXRpb24uIFRoaXMgY29ycmVzcG9uZHMgdG8gdGhlIHN0ZXAgJEYkIG9mIHRoZSBwcm9jZWR1cmUuDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZ1bmN0aW9uPC91Pjwvc3Bhbj4gOiBgZml0TG9jYWxNb2RlbHNgDQotLS0tDQoNClRoaXMgZnVuY3Rpb24gZml0cyBsb2NhbCBtb2RlbHMgb24gYWxsIGNsdXN0ZXJzIG9mIHRoZSBvYnRhaW5lZCBwYXJ0aXRpb24uDQoNCi0gKipBcmd1bWVudCoqOg0KDQogICAgLSBga21lYW5zX0JEYCA6IEFuIG9iamVjdCBvYnRhaW5lZCBmcm9tIGBrbWVhbnNCRGAgZnVuY3Rpb24uDQogICAgLSBgdHJhaW5fcmVzcG9uc2VgIDogVGhlIHZlY3RvciBvZiByZXNwb25zZSB2YXJpYWJsZSBjb3JyZXNwb25kaW5nIHRvIHRoZSBmdWxsIGBpbnB1dF9kYXRhYCBnaXZlbiB0byBga21lYW5CRGAgZnVuY3Rpb24uDQogICAgLSBgbW9kZWxgIDogVHlwZSBvZiBsb2NhbCBtb2RlbCB0byBmaXQgb24gYWxsIHRoZSBjbHVzdGVycyBvZiB0aGUgZ2l2ZW4gcGFydGl0aW9uLiBJdCBzaG91bGQgYmUgZWl0aGVyIGEgbW9kZWwgb2JqZWN0IHdoaWNoIGlzIGNvbXBhY3RpYmxlDQogICAgLSBgZm9ybXVsYWAgOiBUaGUgZGVncmVlIG9mIHBvbHlub21pYWwgQkQgKGlmIG9uZSBpcyB1c2VkKS4NCg0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCiAgICAtIGBsb2NhbF9tb2RlbHNgIDogYWxsIHRoZSBsb2NhbCBtb2RlbHMgZml0dGVkIG9uIGFsbCBjbHVzdGVycyBvZiB0aGUgZ2l2ZW4gcGFydGl0aW9uLg0KICAgIC0gYGttZWFuc19CRGAgOiB0aGUgYGttZWFuc0JEYCBvYmplY3QuDQogICAgLSBgZGF0YV9yZW1haW5gIDogYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0Og0KICAgICAgICAtIGBmaXRgIDogdGhlIGZpdHRlZCB2YWx1ZXMgb2YgdGhlIHJlbWFpbmluZyBwYXJ0IG9mIHRoZSBpbnB1dCBkYXRhLg0KICAgICAgICAtIGByZXNwb25zZWAgOiB0aGUgYWN0dWFsIHJlc3BvbnNlIHZhbHVlcyBjb3JyZXNwb25kaW5nIHRvIHRoZSByZW1haW5pbmcgaW5wdXQgZGF0YS4NCiAgICAtIGBydW5uaW5nX3RpbWVgIDogdGhlIGNvbXB1dGF0aW9uYWwgdGltZSBvZiB0aGUgYWxnb3JpdGhtLg0KDQpgYGB7cn0NCmZpdExvY2FsTW9kZWxzIDwtIGZ1bmN0aW9uKGttZWFuc19CRCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSAibG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9ybXVsYSA9IE5VTEwpew0KICBzdGFydF90aW1lIDwtIFN5cy50aW1lKCkNCiAgWF90cmFpbiA8LSBrbWVhbnNfQkQkdHJhaW5fZGF0YSRYX3RyYWluDQogIHlfdHJhaW4gPC0gdHJhaW5fcmVzcG9uc2VbIShrbWVhbnNfQkQkdHJhaW5fZGF0YSRpZF9yZW1haW4pXQ0KICBYX3JlbWFpbiA8LSBrbWVhbnNfQkQkdHJhaW5fZGF0YSRYX3JlbWFpbg0KICB5X3JlbWFpbiA8LSBOVUxMDQogIGlmKCFpcy5udWxsKFhfcmVtYWluKSl7DQogICAgeV9yZW1haW4gPC0gdHJhaW5fcmVzcG9uc2Vba21lYW5zX0JEJHRyYWluX2RhdGEkaWRfcmVtYWluXQ0KICB9DQogIHBhY21hbjo6cF9sb2FkKHRyZWUpDQogIHBhY21hbjo6cF9sb2FkKHJhbmRvbUZvcmVzdCkNCiAgbW9kZWxfIDwtIGlmZWxzZShtb2RlbCA9PSAidHJlZSIsIHRyZWU6OnRyZWUsIG1vZGVsKQ0KICBLIDwtIG5yb3coa21lYW5zX0JEJGNlbnRyb2lkcykNCiAgaWYgKGlzLm51bGwoZm9ybXVsYSkpew0KICAgIGZvcm0gPC0gZm9ybXVsYSh0YXJnZXQgfiAuKQ0KICB9DQogIGVsc2V7DQogICAgZm9ybSA8LSB1cGRhdGUoZm9ybXVsYSwgdGFyZ2V0IH4gLikNCiAgfQ0KICBkYXRhXyA8LSBiaW5kX2NvbHMoWF90cmFpbiwgInRhcmdldCI6PSB5X3RyYWluKQ0KICBmaXRfbG9va3VwIDwtIGxpc3QobG0gPSAiZml0dGVkLnZhbHVlcyIsDQogICAgICAgICAgICAgICAgICAgICByZiA9ICJwcmVkaWN0ZWQiKQ0KICBpZihpcy5jaGFyYWN0ZXIobW9kZWxfKSl7DQogICAgbW9kZWxfbG9va3VwIDwtIGxpc3QobG0gPSBsbSwNCiAgICAgICAgICAgICAgICAgICAgICAgICByZiA9IHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KQ0KICAgIG1vZCA8LSBtYXAoLnggPSAxOkssIA0KICAgICAgICAgICAgICAgLmYgPSB+IG1vZGVsX2xvb2t1cFtbbW9kZWxfXV0oZm9ybXVsYSA9IGZvcm0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfW2ttZWFuc19CRCRjbHVzdGVycyA9PSAueCwgXSkpDQogIH0gZWxzZXsNCiAgICBtb2QgPC0gbWFwKC54ID0gMTpLLCANCiAgICAgICAgICAgICAgIC5mID0gfiBtb2RlbF8oZm9ybXVsYSA9IGZvcm0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0YV9ba21lYW5zX0JEJGNsdXN0ZXJzID09IC54LF0pKQ0KICB9DQogIHByZWQwIDwtIE5VTEwNCiAgaWYoIWlzLm51bGwoWF9yZW1haW4pKXsNCiAgICBwcmVkMCA8LSB2ZWN0b3IobW9kZSA9ICJudW1lcmljIiwgDQogICAgICAgICAgICAgICAgICAgIGxlbmd0aCA9IGxlbmd0aCh5X3JlbWFpbikpDQogICAgY2x1cyA8LSBmaW5kQ2xvc2VzdENlbnRyb2lkKHguID0gWF9yZW1haW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRyb2lkcy4gPSBrbWVhbnNfQkQkY2VudHJvaWRzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXYgPSBrbWVhbnNfQkQkcGFyYW1ldGVycyRkaXYsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZyA9IGttZWFuc19CRCRwYXJhbWV0ZXJzJGRlZykNCiAgICBmb3IoaV8gaW4gMTpLKXsNCiAgICAgIHByZWQwW2NsdXMgPT0gaV9dIDwtIHByZWRpY3QobW9kW1tpX11dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKFhfcmVtYWluW2NsdXMgPT0gaV8sXSkpDQogICAgfQ0KICB9DQogIHRpbWVfdGFrZW4gPC0gU3lzLnRpbWUoKSAtIHN0YXJ0X3RpbWUNCiAgcmV0dXJuKGxpc3QoDQogICAgbG9jYWxfbW9kZWxzID0gbW9kLA0KICAgIGttZWFuc19CRCA9IGttZWFuc19CRCwNCiAgICBkYXRhX3JlbWFpbiA9IGxpc3QoZml0ID0gcHJlZDAsDQogICAgICAgICAgICAgICAgICAgICAgIHJlc3BvbnNlID0geV9yZW1haW4pLA0KICAgIHJ1bm5pbmdfdGltZSA9IHRpbWVfdGFrZW4NCiAgKSkNCn0NCmBgYA0KDQotLS0tIA0KDQo+ICoqRXhhbXBsZS4yKio6IEZyb20gKipFeGFtcGxlLjEqKiBhYm92ZSwgbXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWxzIGFyZSBidWlsdCBvbiBhbGwgdGhlIG9idGFpbmVkIGNsdXN0ZXJzLiBUaGUgbWVhbiBzcXVhcmUgZXJyb3Igb2YgdGhpcyBtb2RlbCwgZXZhbHVhdGVkIG9uIHRoZSByZW1haW5pbmcgJDUwXCUkIG9mIHRoZSB0cmFpbmluZyBkYXRhIGlzIGNvbXB1dGVkLg0KDQotLS0tDQoNCmBgYHtyfQ0KZml0IDwtIGZpdExvY2FsTW9kZWxzKHRyYWluX3Jlc3BvbnNlID0gZGYkUmluZ3NbdHJhaW5dLA0KICAgICAgICAgICAgICAgICAgICAgIGttZWFuc19CRCA9IGNsLA0KICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gImxtIikNCg0KbWVhbigoZml0JGRhdGFfcmVtYWluJHJlc3BvbnNlLSBmaXQkZGF0YV9yZW1haW4kZml0KV4yKQ0KYGBgDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZ1bmN0aW9uPC91Pjwvc3Bhbj4gOiBgbG9jYWxQcmVkaWN0YA0KLS0tLQ0KDQpUaGlzIGZ1bmN0aW9uIGFsbG93cyB1cyB0byBwcmVkaWN0IGFueSBuZXcgb2JzZXJ2YXRpb24gdXNpbmcgdGhlIGNhbmRpZGF0ZSBtb2RlbCAkXGNhbCBNX2o9KHtcY2FsIE19X3tqLGt9KV97az0xfV5NJCBjb3JyZXNwb25kaW5nIHRvIEJyZWdtYW4gZGl2ZXJnZW5jZSAke1xjYWwgQn1faiQsIGZvciBzb21lICRqXGluIEpcc3Vic2V0XHsxLC4uLixNXH0kLiANCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGBsb2NhbE1vZGVsc2AgOiBUaGUgbG9jYWwgbW9kZWwgb2JqZWN0IG9idGFpbmVkIGZyb20gYGZpdExvY2FsTW9kZWxzYCBmdW5jdGlvbi4NCiAgICAtIGBuZXdEYXRhYCA6IE5ldyBpbnB1dCBkYXRhIHRvIGJlIHByZWRpY3RlZCB1c2luZyB0aGUgY2FuZGlkYXRlIG1vZGVscyBnaXZlbiBpbiBgbG9jYWxNb2RlbHNgIGFyZ3VtZW50Lg0KICAgIA0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSBwcmVkaWN0ZWQgdmVjdG9yIG9mIHRoZSBgbmV3RGF0YWAuDQoNCmBgYHtyfQ0KbG9jYWxQcmVkaWN0IDwtIGZ1bmN0aW9uKGxvY2FsTW9kZWxzLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG5ld0RhdGEpew0KICBrbWVhbl9CRCA8LSBsb2NhbE1vZGVscyRrbWVhbnNfQkQNCiAgSyA8LSBucm93KGttZWFuX0JEJGNlbnRyb2lkcykNCiAgbmV3RGF0YV8gPC0gbmV3RGF0YQ0KICBpZighKGlzLm51bGwoa21lYW5fQkQkcGFyYW1ldGVycyRjZW50ZXJfKSkpew0KICAgIG5ld0RhdGFfIDwtIHNjYWxlKG5ld0RhdGEsDQogICAgICAgICAgICAgICAgICAgICAgY2VudGVyID0ga21lYW5fQkQkcGFyYW1ldGVycyRjZW50ZXJfLA0KICAgICAgICAgICAgICAgICAgICAgIHNjYWxlID0ga21lYW5fQkQkcGFyYW1ldGVycyRzY2FsZV8pDQogICAgaWQwIDwtIChuZXdEYXRhXyA8PSAwKQ0KICAgIGlmKHN1bShpZDApID4gMCl7DQogICAgICBtaW5fIDwtIG1pbihuZXdEYXRhX1tpZDBdKQ0KICAgICAgbmV3RGF0YV9baWQwXSA8LSBydW5pZihzdW0oaWQwKSwgbWluKDFlLTMsIG1pbl8vMTApLCBtaW5fKQ0KICAgIH0NCiAgfQ0KICBjbHVzIDwtIGZpbmRDbG9zZXN0Q2VudHJvaWQoeC4gPSBuZXdEYXRhXywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRyb2lkcy4gPSBrbWVhbl9CRCRjZW50cm9pZHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXYgPSBrbWVhbl9CRCRwYXJhbWV0ZXJzJGRpdiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZyA9IGttZWFuX0JEJHBhcmFtZXRlcnMkZGVnKQ0KICBwcmVkMCA8LSB2ZWN0b3IobW9kZSA9ICJudW1lcmljIiwgbGVuZ3RoID0gbnJvdyhuZXdEYXRhXykpDQogIGZvcihpXyBpbiAxOkspew0KICAgIHByZWQwW2NsdXMgPT0gaV9dIDwtIHByZWRpY3QobG9jYWxNb2RlbHMkbG9jYWxfbW9kZWxzW1tpX11dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZShuZXdEYXRhX1tjbHVzID09IGlfLF0pKQ0KICB9DQogIHByZWQwIDwtIGFzX3RpYmJsZShwcmVkMCkNCiAgbmFtZXMocHJlZDApIDwtIGlmZWxzZShrbWVhbl9CRCRwYXJhbWV0ZXJzJGRpdiA9PSAicG9seW5vbWlhbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJwb2x5bm9taWFsIiwga21lYW5fQkQkcGFyYW1ldGVycyRkZWcpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGttZWFuX0JEJHBhcmFtZXRlcnMkZGl2KQ0KICByZXR1cm4ocHJlZDApDQp9DQpgYGANCg0KLS0tLQ0KDQo+ICoqRXhhbXBsZS4zKio6IFRoZSB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIGNhbmRpZGF0ZSBtb2RlbCBjb3JyZXNwb25kaW5nIHRvIGAiZ2tsImAgZGl2ZXJnZW5jZSBpcyBjb21wYXJlZCB0byByYW5kb20gZm9yZXN0IHJlZ3Jlc3Npb24gb24gYSAkMjBcJSQgdGVzdGluZyBkYXRhLg0KDQotLS0tDQoNCmBgYHtyfQ0KeV9oYXQgPC0gbG9jYWxQcmVkaWN0KGZpdCwNCiAgICAgICAgICAgICAgICAgICAgICBkZlshdHJhaW4sIDI6KG5jb2woZGYpLTEpLF0pDQpyZiA8LSByYW5kb21Gb3Jlc3QoUmluZ3MgfiAuLCBkYXRhID0gZGZbdHJhaW4sMjpuY29sKGRmKV0pDQptZWFuKChwcmVkaWN0KHJmLCBuZXdkYXRhID0gZGZbIXRyYWluLDI6bmNvbChkZildKS0gZGYkUmluZ3NbIXRyYWluXSleMikNCm1lYW4oKHlfaGF0JGdrbC1kZiRSaW5nc1shdHJhaW5dKV4yKQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+U3RlcCAkQyQ6IENvbWJpbmluZyBtZXRob2RzPC91Pjwvc3Bhbj4NCj09PQ0KDQpUaGUgYWdncmVnYXRpb24gbWV0aG9kcyBhcmUgYXZhaWxhYmxlIFtoZXJlIDxzcGFuIHN0eWxlPSJjb2xvcjogIzA5N0JDMSI+IGByIGZvbnRhd2Vzb21lOjpmYSgiZ2l0aHViIilgPC9zcGFuPl0oaHR0cHM6Ly9naXRodWIuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMpLiBUaGUgYWdncmVnYXRpb24gbWV0aG9kcyBhcmUgaW1wb3J0ZWQgaW50byA8c3BhbiBzdHlsZT0iY29sb3I6ICMwMjg3RDg7Ij4gKipSc3R1ZGlvKiogPC9zcGFuPiBlbnZpcm9ubWVudC4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpwYWNtYW46OnBfbG9hZChkZXZ0b29scykNCiMjIyBLZXJuZWwgYmFzZWQgY29uc2Vuc3VhbCBhZ2dyZWdhdGlvbg0Kc291cmNlX3VybCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMvbWFpbi9LZXJuZWxBZ2dSZWcuUiIpDQojIyMgTWl4Q29icmENCnNvdXJjZV91cmwoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9oYXNzb3RoZWEvQWdncmVnYXRpb25NZXRob2RzL21haW4vTWl4Q29icmFSZWcuUiIpDQpgYGANCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPiA6IGBzdGVwS2AsIGBzdGVwRmAgYW5kIGBzdGVwQ2ANCi0tLS0NCg0KVGhlc2UgZnVuY3Rpb25zIGFsbG93IHRvIHNldCB0aGUgdmFsdWVzIG9mIHRoZSBwYXJhbWV0ZXJzIGluIHRoZSB0aHJlZSBzdGVwcyBvZiB0aGUgS0ZDIHByb2NlZHVyZS4gRWFjaCBmdW5jdGlvbiByZXR1cm5zIGEgbGlzdCBvZiBhbGwgdGhlIHBhcmFtZXRlcnMgZ2l2ZW4gaW4gaXRzIGFyZ3VtZW50cy4NCg0KYGBge3J9DQpzdGVwSyA9IGZ1bmN0aW9uKEssDQogICAgICAgICAgICAgICAgIG5fc3RhcnQgPSA1LA0KICAgICAgICAgICAgICAgICBtYXhJdGVyID0gMzAwLA0KICAgICAgICAgICAgICAgICBkZWcgPSAzLA0KICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICBkaXYgPSBOVUxMLA0KICAgICAgICAgICAgICAgICBzcGxpdHMgPSAwLjc1LA0KICAgICAgICAgICAgICAgICBlcHNpbG9uID0gMWUtMTAsDQogICAgICAgICAgICAgICAgIGNlbnRlcl8gPSBOVUxMLA0KICAgICAgICAgICAgICAgICBzY2FsZV8gPSBOVUxMKXsNCiAgcmV0dXJuKGxpc3QoSyA9IEssDQogICAgICAgICAgICAgIG5fc3RhcnQgPSBuX3N0YXJ0LA0KICAgICAgICAgICAgICBtYXhJdGVyID0gbWF4SXRlciwNCiAgICAgICAgICAgICAgZGVnID0gZGVnLA0KICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IHNjYWxlX2lucHV0LA0KICAgICAgICAgICAgICBkaXYgPSBkaXYsDQogICAgICAgICAgICAgIHNwbGl0cyA9IHNwbGl0cywNCiAgICAgICAgICAgICAgZXBzaWxvbiA9IGVwc2lsb24sDQogICAgICAgICAgICAgIGNlbnRlcl8gPSBjZW50ZXJfICwNCiAgICAgICAgICAgICAgc2NhbGVfID0gc2NhbGVfKSkNCn0NCg0Kc3RlcEYgPSBmdW5jdGlvbihmb3JtdWxhID0gTlVMTCwgDQogICAgICAgICAgICAgICAgIG1vZGVsID0gImxtIil7DQogIHJldHVybihsaXN0KGZvcm11bGEgPSBmb3JtdWxhLCANCiAgICAgICAgICAgICAgbW9kZWwgPSBtb2RlbCkpDQp9DQoNCnN0ZXBDID0gZnVuY3Rpb24obl9jdiA9IDUsDQogICAgICAgICAgICAgICAgIG1ldGhvZCA9IGMoImNvYnJhIiwgIm1peGNvYnJhIiksDQogICAgICAgICAgICAgICAgIG9wdF9tZXRob2RzID0gYygiZ3JhZCIsICJncmlkIiksDQogICAgICAgICAgICAgICAgIGtlcm5lbHMgPSAiZ2F1c3NpYW4iLA0KICAgICAgICAgICAgICAgICBzY2FsZV9mZWF0dXJlcyA9IEZBTFNFKXsNCiAgcmV0dXJuKGxpc3Qobl9jdiA9IG5fY3YsDQogICAgICAgICAgICAgIG1ldGhvZCA9IG1ldGhvZCwNCiAgICAgICAgICAgICAgb3B0X21ldGhvZHMgPSBvcHRfbWV0aG9kcywNCiAgICAgICAgICAgICAga2VybmVscyA9IGtlcm5lbHMsDQogICAgICAgICAgICAgIHNjYWxlX2ZlYXR1cmVzID0gc2NhbGVfZmVhdHVyZXMpKQ0KfQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPjogYEtGQ3JlZ2ANCj09PQ0KDQpUaGlzIGZ1bmN0aW9uIGlzIHRoZSBjb21wbGV0ZSBpbXBsZW1lbnRhdGlvbiBvZiBLRkMgcHJvY2VkdXJlLg0KDQotICoqQXJndW1lbnQqKjoNCg0KICAgIC0gYHRyYWluX2lucHV0YCA6IFRoZSBtYXRyaXggb3IgZGF0YSBmcmFtZSBvZiB0cmFpbmluZyBpbnB1dCBkYXRhLg0KICAgIC0gYHRyYWluX3Jlc3BvbnNlYCA6IFRoZSB0cmFpbmluZyByZXNwb25zZSB2YXJpYWJsZS4NCiAgICAtIGB0ZXN0X2lucHV0YDogVGhlIHRlc3RpbmcgaW5wdXQgZGF0YS4NCiAgICAtIGB0ZXN0X3Jlc3BvbnNlYCA6IFRoZSByZXNwb25zZSB2YXJpYWJsZSBvZiB0aGUgdGVzdGluZyBkYXRhLiBJdCBpcyBvcHRpb25hbC4gSWYgaXQgaXMgbm90IGBOVUxMYCwgdGhlIG1lYW4gc3F1YXJlIGVycm9yIChtc2UpIGlzIGNvbXB1dGVkLg0KICAgIC0gYG5fY3ZgIDogVGhlIG51bWJlciBvZiBmb2xkcyBpbiBjcm9zcy12YWxpZGF0aW9uLg0KICAgIC0gYHBhcmFsbGVsYCA6IEEgbG9naWNhbCB2YWx1ZSBzcGVjaWZ5aW5nIHdoZXRoZXIgb3Igbm90IHRvIHBlcmZvcm0gdGhlIHN0ZXAgJEskIGluIHBhcmFsbGVsLiBCeSBkZWZhdWx0LCBgcGFyYWxsZWwgPSBUUlVFYCwgYW5kIG5vdGUgdGhhdCBpbnRlcm5ldCBjb25uZWN0aW9uIGlzIHJlcXVpcmVkIGluIHRoaXMgY2FzZS4NCiAgICAtIGBpbnZfc2lnbWFgLCBgYWxwYCA6IHRoZSBpbnZlcnNlIG5vcm1hbGl6ZWQgY29uc3RhbnQgJFxzaWdtYV57LTF9PjAkIGFuZCB0aGUgZXhwb25lbnQgJFxhbHBoYT4wJCBvZiBleHBvbmVudGlhbCBrZXJuZWw6ICRLKHgpPWVeey1cfHgvXHNpZ21hXHxee1xhbHBoYX19JCBmb3IgYW55ICR4XGluXG1hdGhiYntSfV5kJC4gQnkgZGVmYXVsdCwgYGludl9zaWdtYSA9IGAkXHNxcnR7MS8yfSQgYW5kIGBhbHBoYSA9IDJgIHdoaWNoIGNvcnJlc3BvbmRzIHRvIHRoZSBHYXVzc2lhbiBrZXJuZWwuDQogICAgLSBgS19zdGVwYCA6IFRoZSBvYmplY3Qgb2J0YWluZWQgZnJvbSBmdW5jdGlvbiBgc3RlcEtgIHdoaWNoIGFsbG93aW5nIHRvIHNldCB0aGUgdmFsdWVzIG9mIHRoZSBwYXJhbXRlcnMgaW4gc3RlcCAkSyQgb2YgdGhlIHByb2NlZHVyZS4NCiAgICAtIGBGX3N0ZXBgIDogVGhlIG9iamVjdCBvYnRhaW5lZCBmcm9tIGZ1bmN0aW9uIGBzdGVwRmAgd2hpY2ggYWxsb3dpbmcgdG8gc2V0IHRoZSB2YWx1ZXMgb2YgdGhlIHBhcmFtdGVycyBpbiBzdGVwICRGJCBvZiB0aGUgcHJvY2VkdXJlLg0KICAgIC0gYENfc3RlcGAgOiBUaGUgb2JqZWN0IG9idGFpbmVkIGZyb20gZnVuY3Rpb24gYHN0ZXBDYCB3aGljaCBhbGxvd2luZyB0byBzZXQgdGhlIHZhbHVlcyBvZiB0aGUgcGFyYW10ZXJzIGluIHN0ZXAgJEMkIG9mIHRoZSBwcm9jZWR1cmUuDQogICAgLSBgc2V0R3JhZFBhcmFtQWdnYCA6IFRoZSBvYmplY3QgZnJvbSB0aGUgYHNldEdyYWRQYXJhbWV0ZXJgIGZ1bmN0aW9uLCBhbGxvd2luZyB0byBzZXQgdGhlIHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVycyBvZiAqKmdyYWRpZW50IGRlc2NlbnQqKiBhbGdvcml0aG0gZm9yIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICNFNjE4MEE7Ij4qKjFzdCBhZ2dyZWdhdGlvbiBtZXRob2QkXjEkKio8L3NwYW4+Lg0KICAgIC0gYHNldEdyaWRQYXJhbUFnZ2AgOiBUaGUgb2JqZWN0IGZyb20gdGhlIGBzZXRHcmlkUGFyYW1ldGVyYCBmdW5jdGlvbiwgYWxsb3dpbmcgdG8gc2V0IHRoZSB2YWx1ZXMgb2YgdGhlIHBhcmFtZXRlcnMgb2YgdGhlICoqZ3JpZCBzZWFyY2gqKiBhbGdvcml0aG0gZm9yIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICNFNjE4MEE7Ij4qKjFzdCBhZ2dyZWdhdGlvbiBtZXRob2QkXjEkKio8L3NwYW4+Lg0KICAgIC0gYHNldEdyYWRQYXJhbU1peGAgOiBUaGUgb2JqZWN0IGZyb20gdGhlIGBzZXRHcmFkUGFyYW1ldGVyX01peGAgZnVuY3Rpb24sIGFsbG93aW5nIHRvIHNldCB0aGUgdmFsdWVzIG9mIHRoZSBwYXJhbWV0ZXJzIG9mIHRoZSAqKmdyYWRpZW50IGRlc2NlbnQqKiBhbGdvcml0aG0gZm9yIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwODMyQ0QiPioqMm5kIGFnZ3JlZ2F0aW9uIG1ldGhvZCReMiQqKjwvc3Bhbj4uDQogICAgLSBgc2V0R3JpZFBhcmFtTWl4YCA6IFRoZSBvYmplY3QgZnJvbSB0aGUgYHNldEdyaWRQYXJhbWV0ZXJfTWl4YCBmdW5jdGlvbiwgYWxsb3dpbmcgdG8gc2V0IHRoZSB2YWx1ZXMgb2YgdGhlIHBhcmFtZXRlcnMgb2YgdGhlICoqZ3JpZCBzZWFyY2gqKiBhbGdvcml0aG0gZm9yIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwODMyQ0QiPioqMm5kIGFnZ3JlZ2F0aW9uIG1ldGhvZCReMiQqKjwvc3Bhbj4uDQogICAgDQotICoqVmFsdWUqKjoNCg0KICAgIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhIGxpc3Qgb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzOg0KICAgIA0KICAgIC0gYHByZWRpY3RfZmluYWxgIDogdGhlIGZpbmFsIHByZWRpY3Rpb25zIGdpdmVuIGJ5IHRoZSBhZ2dyZWdhdGlvbiBvZiBhbGwgdGhlIGNhbmRpZGF0ZSBtb2RlbHMuDQogICAgLSBgcHJlZGljdF9sb2NhbGAgOiB0aGUgcHJlZGljdGlvbnMgZ2l2ZW4gYnkgYWxsIHRoZSBpbmRpdmlkdWFsIGNhbmRpZGF0ZSBtb2RlbHMuDQogICAgLSBgYWdnX21ldGhvZGAgOiB0aGUgbGlzdCBvZiBhZ2dyZWdhdGlvbiBtZXRob2RzIG9idGFpbmVkIGluIHRoZSBzdGVwICRDJCBvZiB0aGUgcHJvY2VkdXJlLg0KICAgIC0gYHJ1bm5pbmdfdGltZWAgOiB0aGUgY29tcHV0YXRpb25hbCB0aW1lIG9mIHRoZSBhbGdvcml0aG0uDQoNCi0tLS0NCg0KICA+ICoqUmVtYXJrLjIqKjogVGhlIGBwYXJhbGxlbGAgYXJndW1lbnQgYWJvdmUgcmVxdWlyZXMgaW50ZXJuZXQgY29ubmVjdGlvbiB0byBsb2FkIHRoZSBzb3VyY2UgY29kZXMgb2YgJEskLW1lYW5zIGFsZ29yaXRobSB3aXRoIEJEcyBmcm9tIFtHaXRIdWIgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDk3QkMxIj4gYHIgZm9udGF3ZXNvbWU6OmZhKCJnaXRodWIiKWA8L3NwYW4+XShodHRwczovL2dpdGh1Yi5jb20vaGFzc290aGVhL0tGQy1Qcm9jZWR1cmUvYmxvYi9tYXN0ZXIva21lYW5CRC5SKS4gSXQgaXMgcGVyZm9ybWVkIG9uIHRoZSBtYXhpbXVtIG51bWJlciBvZiBjbHVzdGVycyBvZiB5b3VyIG1hY2hpbmUsIGFuZCB0aGUgc3BlZWQgaXMgYXQgbGVhc3QgdHdvIHRpbWVzIGZhc3RlciB0aGFuIHdpdGhvdXQgcGFyYWxsZWxpc20sIGhvd2V2ZXIsIGl0IGlzIG5vdCBzbyBzdGFibGUgZGVwZW5kaW5nIG9uIHlvdXIgaW50ZXJuZXQgY29ubmVjdGlvbiBvciBtYWNoaW5lLiBBYm91dCB0aGUgYWdncmVnYXRpb24gbWV0aG9kcyBvZiBzdGVwICRDJCwNCg0KDQotIDxzcGFuIHN0eWxlPSJjb2xvcjogI0U2MTgwQTsiPiReMSQ8L3NwYW4+IGlzIHRoZSBrZXJuZWwtYmFzZWQgY29uc2Vuc3VhbCBhZ2dyZWdhdGlvbiBmb3IgcmVncmVzc2lvbiBieSBbSGFzICgyMDIxKV0oaHR0cHM6Ly9oYWwuYXJjaGl2ZXMtb3V2ZXJ0ZXMuZnIvaGFsLTAyODg0MzMzdjUpLiBUaGUgc291cmNlIGNvZGVzIG9mIHRoZXNlIGZ1bmN0aW9ucyBhcmUgYXZhaWxhYmxlIGluIFtLZXJuZWxBZ2dSZWcuUl0oaHR0cHM6Ly9naXRodWIuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMvYmxvYi9tYWluL0tlcm5lbEFnZ1JlZy5SKSwgYW5kIHRoZSBkb2N1bWVudGF0aW9uIGlzIGF2YWlsYWJsZSBbaGVyZV0oaHR0cHM6Ly9oYXNzb3RoZWEuZ2l0aHViLmlvL2ZpbGVzL0tlcm5lbEFnZ1JlZy9LZXJuZWxBZ2dSZWcuaHRtbCkuDQotIDxzcGFuIHN0eWxlPSJjb2xvcjogIzA4MzJDRCI+JF4yJDwvc3Bhbj4gaXMgdGhlIGFnZ3JlZ2F0aW9uIHVzaW5nIGlucHV0LW91dHB1dCB0cmFkZS1vZmYgYnkgW0Zpc2NoZXIgYW5kIE1vdWdlb3QgKDIwMTkpXShodHRwczovL3d3dy5zY2llbmNlZGlyZWN0LmNvbS9zY2llbmNlL2FydGljbGUvcGlpL1MwMzc4Mzc1ODE4MzAyMzQ5KS4gVGhlIHNvdXJjZSBjb2RlcyBvZiB0aGVzZSBmdW5jdGlvbnMgYXJlIGF2YWlsYWJsZSBpbiBbTWl4Q29icmEuUl0oaHR0cHM6Ly9naXRodWIuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMvYmxvYi9tYWluL01peENvYnJhUmVnLlIpLCBhbmQgdGhlIGRvY3VtZW50YXRpb24gaXMgYXZhaWxhYmxlIG9uIFtoZXJlXShodHRwczovL2hhc3NvdGhlYS5naXRodWIuaW8vZmlsZXMvS2VybmVsQWdnUmVnL01peENvYnJhUmVnLmh0bWwpLg0KDQotLS0tDQoNCg0KYGBge3J9DQpLRkNyZWcgPSBmdW5jdGlvbih0cmFpbl9pbnB1dCwNCiAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgdGVzdF9pbnB1dCwNCiAgICAgICAgICAgICAgICAgIHRlc3RfcmVzcG9uc2UgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgbl9jdiA9IDUsDQogICAgICAgICAgICAgICAgICBwYXJhbGxlbCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICBpbnZfc2lnbWEgPSBzcXJ0KC41KSwNCiAgICAgICAgICAgICAgICAgIGFscCA9IDIsDQogICAgICAgICAgICAgICAgICBLX3N0ZXAgPSBzdGVwSyhzcGxpdHMgPSAuNSksDQogICAgICAgICAgICAgICAgICBGX3N0ZXAgPSBzdGVwRigpLA0KICAgICAgICAgICAgICAgICAgQ19zdGVwID0gc3RlcEMoKSwNCiAgICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbUFnZyA9IHNldEdyYWRQYXJhbWV0ZXIoKSwNCiAgICAgICAgICAgICAgICAgIHNldEdyaWRQYXJhbUFnZyA9IHNldEdyaWRQYXJhbWV0ZXIoKSwNCiAgICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbU1peCA9IHNldEdyYWRQYXJhbWV0ZXJfTWl4KCksDQogICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW1NaXggPSBzZXRHcmlkUGFyYW1ldGVyX01peCgpKXsNCiAgc3RhcnRfdGltZSA8LSBTeXMudGltZSgpDQogIGxvb2t1cF9kaXZfbmFtZXMgPC0gYygiZXVjbGlkZWFuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAiZ2tsIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAibG9naXN0aWMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJpdGFrdXJhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAicG9seW5vbWlhbCIpDQogIGRpdl8gPC0gS19zdGVwJGRpdg0KICAjIyMgSyBzdGVwOiBLbWVhbnMgY2x1c3RlcmluZyB3aXRoIEJEcw0KICBpZiAoaXMubnVsbChLX3N0ZXAkZGl2KSl7DQogICAgZGl2ZXJnZW5jZXMgPC0gbG9va3VwX2Rpdl9uYW1lcw0KICAgIHdhcm5pbmcoIk5vIGRpdmVyZ2VuY2UgcHJvdmlkZWQhIEFsbCBvZiB0aGVtIGFyZSB1c2VkISIpDQogIH0NCiAgZWxzZXsNCiAgICBkaXZlcmdlbmNlcyA8LSBLX3N0ZXAkZGl2ICU+JSANCiAgICAgIG1hcF9jaHIoLmYgPSB+IG1hdGNoLmFyZyhhcmcgPSAueCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2hvaWNlcyA9IGxvb2t1cF9kaXZfbmFtZXMpKQ0KICB9DQogIGRpdl9saXN0IDwtIGRpdmVyZ2VuY2VzICU+JSANCiAgICBtYXAoLmYgPSAoXCh4KSBpZih4ICE9ICJwb2x5bm9taWFsIikgcmV0dXJuKHgpIGVsc2UgcmV0dXJuKHJlcCgicG9seW5vbWlhbCIsIGxlbmd0aChLX3N0ZXAkZGVnKSkpKSkgJT4lDQogICAgdW5saXN0DQogIGRlZ19saXN0IDwtIHJlcChOQSwgbGVuZ3RoKGRpdl8pKQ0KICBkZWdfbGlzdFtkaXZfbGlzdCA9PSAicG9seW5vbWlhbCJdIDwtIEtfc3RlcCRkZWcNCiAgZGl2X25hbWVzIDwtIG1hcDJfY2hyKC54ID0gZGl2X2xpc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAueSA9IGRlZ19saXN0LA0KICAgICAgICAgICAgICAgICAgICAgICAgLmYgPSAoXCh4LCB5KSBpZihpcy5uYSh5KSkgcmV0dXJuKHgpIGVsc2UgcmV0dXJuKHBhc3RlMCh4LHkpKSkpDQogICMjIyBTdGVwIEs6IEttZWFucyBjbHVzdGVyaW5nIHdpdGggQnJlZ21hbiBkaXZlcmdlbmNlcw0KICBkbSA8LSBkaW0odHJhaW5faW5wdXQpDQogIGlkX3NodWZmbGUgPC0gdmVjdG9yKGxlbmd0aCA9IGRtWzFdKQ0KICBuX3RyYWluIDwtIGZsb29yKEtfc3RlcCRzcGxpdHMgKiBkbVsxXSkNCiAgaWRfc2h1ZmZsZVtzYW1wbGUoZG1bMV0sIG5fdHJhaW4pXSA8LSBUUlVFDQogIGlmKHBhcmFsbGVsKXsNCiAgICBudW1Db3JlcyA8LSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKQ0KICAgIGRvUGFyYWxsZWw6OnJlZ2lzdGVyRG9QYXJhbGxlbChudW1Db3JlcykgIyB1c2UgbXVsdGljb3JlLCBzZXQgdG8gdGhlIG51bWJlciBvZiBvdXIgY29yZXMNCiAgICBrbWVhbl8gPC0gZm9yZWFjaChpPTE6bGVuZ3RoKGRpdl9uYW1lcykpICVkb3BhciUgew0KICAgICAgZGV2dG9vbHM6OnNvdXJjZV91cmwoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9oYXNzb3RoZWEvS0ZDLVByb2NlZHVyZS9tYXN0ZXIva21lYW5CRC5SIikNCiAgICAgIGttZWFuc0JEKHRyYWluX2lucHV0ID0gdHJhaW5faW5wdXQsDQogICAgICAgICAgICAgICBLID0gS19zdGVwJEssDQogICAgICAgICAgICAgICBkaXYgPSBkaXZfbGlzdFtpXSwNCiAgICAgICAgICAgICAgIG5fc3RhcnQgPSBLX3N0ZXAkbl9zdGFydCwNCiAgICAgICAgICAgICAgIG1heEl0ZXIgPSBLX3N0ZXAkbWF4SXRlciwNCiAgICAgICAgICAgICAgIGRlZyA9IGRlZ19saXN0W2ldLA0KICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBLX3N0ZXAkc2NhbGVfaW5wdXQsDQogICAgICAgICAgICAgICBzcGxpdHMgPSBLX3N0ZXAkc3BsaXRzLA0KICAgICAgICAgICAgICAgZXBzaWxvbiA9IEtfc3RlcCRlcHNpbG9uLA0KICAgICAgICAgICAgICAgY2VudGVyXyA9IEtfc3RlcCRjZW50ZXJfLA0KICAgICAgICAgICAgICAgc2NhbGVfID0gS19zdGVwJHNjYWxlXywNCiAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBpZF9zaHVmZmxlKQ0KICAgIH0NCiAgICBkb1BhcmFsbGVsOjpzdG9wSW1wbGljaXRDbHVzdGVyKCkNCiAgfSBlbHNlew0KICAgIGttZWFuXyA8LSBtYXAyKC54ID0gZGl2X2xpc3QsDQogICAgICAgICAgICAgICAgICAgLnkgPSBkZWdfbGlzdCwNCiAgICAgICAgICAgICAgICAgICAuZiA9IH4ga21lYW5zQkQodHJhaW5faW5wdXQgPSB0cmFpbl9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSyA9IEtfc3RlcCRLLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXYgPSAueCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9zdGFydCA9IEtfc3RlcCRuX3N0YXJ0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhJdGVyID0gS19zdGVwJG1heEl0ZXIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZyA9IC55LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IEtfc3RlcCRzY2FsZV9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gS19zdGVwJHNwbGl0cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXBzaWxvbiA9IEtfc3RlcCRlcHNpbG9uLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZW50ZXJfID0gS19zdGVwJGNlbnRlcl8sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlXyA9IEtfc3RlcCRzY2FsZV8sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBpZF9zaHVmZmxlKSkNCiAgfQ0KICBuYW1lcyhrbWVhbl8pIDwtIGRpdl9uYW1lcw0KICAjIyMgRiBzdGVwOiBGaXR0aW5nIHRoZSBjb3JyZXNwb25kaW5nIG1vZGVsIG9uIGVhY2ggb2JzZXJ2ZWQgY2x1c3Rlcg0KICBtb2RlbF8gPC0gZGl2X25hbWVzICU+JQ0KICAgIG1hcCguZiA9IH4gZml0TG9jYWxNb2RlbHMoa21lYW5fW1sueF1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSB0cmFpbl9yZXNwb25zZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gRl9zdGVwJG1vZGVsLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9ybXVsYSA9IEZfc3RlcCRmb3JtdWxhKSkNCiAgbmFtZXMobW9kZWxfKSA8LSBkaXZfbmFtZXMNCiAgcHJlZF9jb21iaW5lIDwtIG1vZGVsXyAlPiUNCiAgICBtYXBfZGZjKC5mID0gfiAueCRkYXRhX3JlbWFpbiRmaXQpDQogIHlfcmVtYWluIDwtIHRyYWluX3Jlc3BvbnNlWyFpZF9zaHVmZmxlXQ0KICBwcmVkX3Rlc3QgPC0gZGl2X25hbWVzICU+JQ0KICAgIG1hcF9kZmMoLmYgPSB+IGxvY2FsUHJlZGljdChtb2RlbF9bWy54XV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RfaW5wdXQpKQ0KICBuYW1lcyhwcmVkX3Rlc3QpIDwtIG5hbWVzKHByZWRfY29tYmluZSkgPC0gZGl2X25hbWVzDQogICMgQyBzdGVwOiBDb25zZW5zdWFsIHJlZ3Jlc3Npb24gYWdncmVnYXRpb24gbWV0aG9kIHdpdGgga2VybmVsLWJhc2VkIENPQlJBDQogIGxpc3RfbWV0aG9kX2FnZyA8LSBsaXN0KG1peGNvYnJhID0gZnVuY3Rpb24ocHJlZCl7TWl4Q29icmFSZWcodHJhaW5faW5wdXQgPSB0cmFpbl9pbnB1dFshaWRfc2h1ZmZsZSxdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlID0geV9yZW1haW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9pbnB1dCA9IHRlc3RfaW5wdXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcHJlZGljdGlvbnMgPSBwcmVkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RfcHJlZGljdGlvbnMgPSBwcmVkX3Rlc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9yZXNwb25zZSA9IHRlc3RfcmVzcG9uc2UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBLX3N0ZXAkc2NhbGVfaW5wdXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfbWFjaGluZSA9IENfc3RlcCRzY2FsZV9mZWF0dXJlcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2N2ID0gQ19zdGVwJG5fY3YsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW52X3NpZ21hID0gaW52X3NpZ21hLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscCA9IGFscCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWxzID0gQ19zdGVwJGtlcm5lbHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3B0aW1pemVNZXRob2QgPSBDX3N0ZXAkb3B0X21ldGhvZHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0R3JhZFBhcmFtID0gc2V0R3JhZFBhcmFtTWl4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldEdyaWRQYXJhbSA9IHNldEdyaWRQYXJhbU1peCl9LA0KICAgICAgICAgICAgICAgICAgICAgICAgICBjb2JyYSA9IGZ1bmN0aW9uKHByZWQpe2tlcm5lbEFnZ1JlZyh0cmFpbl9kZXNpZ24gPSBwcmVkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9yZXNwb25zZSA9IHlfcmVtYWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0X2Rlc2lnbiA9IHByZWRfdGVzdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9yZXNwb25zZSA9IHRlc3RfcmVzcG9uc2UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2lucHV0ID0gS19zdGVwJHNjYWxlX2lucHV0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9tYWNoaW5lID0gQ19zdGVwJHNjYWxlX2ZlYXR1cmVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBidWlsZF9tYWNoaW5lID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hY2hpbmVzID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jdiA9IENfc3RlcCRuX2N2LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnZfc2lnbWEgPSBzcXJ0KC41KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwID0gMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVscyA9IENfc3RlcCRrZXJuZWxzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcHRpbWl6ZU1ldGhvZCA9IENfc3RlcCRvcHRfbWV0aG9kcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0R3JhZFBhcmFtID0gc2V0R3JhZFBhcmFtQWdnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW0gPSBzZXRHcmlkUGFyYW1BZ2cpfSkNCiAgcmVzIDwtIG1hcCgueCA9IENfc3RlcCRtZXRob2QsDQogICAgICAgICAgICAgLmYgPSB+IGxpc3RfbWV0aG9kX2FnZ1tbLnhdXShwcmVkX2NvbWJpbmUpKQ0KICBsaXN0X2FnZ19tZXRob2RzIDwtIGxpc3QoY29icmEgPSAiY29iIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1peGNvYnJhID0gIm1peCIpDQogIG5hbWVzKHJlcykgPC0gQ19zdGVwJG1ldGhvZA0KICBleHRfZnVuIDwtIGZ1bmN0aW9uKEwsIG5hbSl7DQogICAgdGFiIDwtIEwkZml0dGVkX2FnZ3JlZ2F0ZQ0KICAgIG5hbWVzKHRhYikgPC0gcGFzdGUwKG5hbWVzKHRhYiksICJfIiwgbmFtKQ0KICAgIHJldHVybih0YWIpDQogIH0NCiAgcHJlZF9maW4gPC0gQ19zdGVwJG1ldGhvZCAlPiUNCiAgICBtYXBfZGZjKC5mID0gfiBleHRfZnVuKHJlc1tbLnhdXSwgbGlzdF9hZ2dfbWV0aG9kc1tbLnhdXSkpDQogIHRpbWUudGFrZW4gPC0gU3lzLnRpbWUoKSAtIHN0YXJ0X3RpbWUNCiAgIyMjIFRvIGZpbmlzaA0KICBpZihpcy5udWxsKHRlc3RfcmVzcG9uc2UpKXsNCiAgICByZXR1cm4obGlzdCgNCiAgICBwcmVkaWN0X2ZpbmFsID0gcHJlZF9maW4sDQogICAgcHJlZGljdF9sb2NhbCA9IHByZWRfdGVzdCwNCiAgICBhZ2dfbWV0aG9kID0gcmVzLA0KICAgIHJ1bm5pbmdfdGltZSA9IHRpbWUudGFrZW4NCiAgKSkNCiAgfSBlbHNlew0KICAgIGVycm9yIDwtIGNiaW5kKHByZWRfdGVzdCwgcHJlZF9maW4pICU+JQ0KICAgICAgZHBseXI6Om11dGF0ZSh5X3Rlc3QgPSB0ZXN0X3Jlc3BvbnNlKSAlPiUNCiAgICAgIGRwbHlyOjpzdW1tYXJpc2VfYWxsKC5mdW5zID0gfiAoLiAtIHlfdGVzdCkpICU+JQ0KICAgICAgZHBseXI6OnNlbGVjdCgteV90ZXN0KSAlPiUNCiAgICAgIGRwbHlyOjpzdW1tYXJpc2VfYWxsKC5mdW5zID0gfiBtZWFuKC5eMikpDQogICAgcmV0dXJuKGxpc3QoDQogICAgICBwcmVkaWN0X2ZpbmFsID0gcHJlZF9maW4sDQogICAgICBwcmVkaWN0X2xvY2FsID0gcHJlZF90ZXN0LA0KICAgICAgYWdnX21ldGhvZCA9IHJlcywNCiAgICAgIG1zZSA9IGVycm9yLA0KICAgICAgcnVubmluZ190aW1lID0gdGltZS50YWtlbg0KICApKQ0KICB9DQp9DQpgYGANCg0KPiAqKkV4YW1wbGUuNCoqOiBBIGNvbXBsZXRlIEtGQyBwcm9jZWR1cmUgaXMgaW1wbGVtZW50ZWQgb24gdGhlIHNhbWUgQWJhbG9uZSBkYXRhLCB1c2luZyAkNiQgQkRzIGAiZXVjbGlkZWFuImAsIGAiaXRha3VyYSJgLCBgImdrbCJgLCBgImxvZ2lzdGljImAgYW5kIGAicG9seW5vbWlhbCJgIChvZiBkZWdyZWUgJDMkIGFuZCAkNiQpLiBCb3RoIGFnZ3JlZ2F0aW9uIG1ldGhvZHMgYXJlIHVzZWQgaW4gdGhlIHN0ZXAgJEMkLiBUd28ga2VybmVsIGZ1bmN0aW9ucyBhcmUgdXNlZCBmb3IgZWFjaCBhZ2dyZWdhdGlvbiBtZXRob2Q6IGAiZ2F1c3NpYW4iYCAod2l0aCBncmFkaWVudCBkZXNjZW50IGFsZ29yaXRobSkgYW5kIGAiZXBhbmVjaG5pa292ImAgKHdpdGggZ3JpZCBzZWFyY2ggYWxnb3JpdGhtKS4NCg0KYGBge3J9DQp0cmFpbjEgPC0gbG9naWNhbChuKQ0KdHJhaW4xW3NhbXBsZShuLCAgZmxvb3IobiowLjgpKV0gPC0gVFJVRQ0Ka2ZjMSA8LSBLRkNyZWcodHJhaW5faW5wdXQgPSBkZlt0cmFpbjEsMjpuY29sKGRmKV0sDQogICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSBkZiRSaW5nc1t0cmFpbjFdLA0KICAgICAgICAgICAgICAgIHRlc3RfaW5wdXQgPSBkZlshdHJhaW4xLDI6bmNvbChkZildLA0KICAgICAgICAgICAgICAgIEtfc3RlcCA9IHN0ZXBLKEsgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2lucHV0ID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXYgPSBjKCJldWNsIiwgIml0YSIsICJna2wiLCAibG9nIiAsInBvbHkiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWcgPSBjKDMsIDYpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0cyA9IC41KSwNCiAgICAgICAgICAgICAgICBDX3N0ZXAgPSBzdGVwQyhtZXRob2QgPSBjKCJjb2JyYSIsICJtaXhjb2JyYSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9wdF9tZXRob2RzID0gYygiZ3JhZCIsICJncmlkIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVscyA9IGMoImdhdXNzaWFuIiwgImdhdXNzaWFuIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfZmVhdHVyZXMgPSBGQUxTRSksDQogICAgICAgICAgICAgICAgc2V0R3JhZFBhcmFtQWdnID0gc2V0R3JhZFBhcmFtZXRlcihyYXRlID0gMSksDQogICAgICAgICAgICAgICAgc2V0R3JpZFBhcmFtQWdnID0gc2V0R3JpZFBhcmFtZXRlcihtaW5fdmFsID0gLjAwMDAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X3ZhbCA9IDEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl92YWwgPSAxMDApLA0KICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbU1peCA9IHNldEdyYWRQYXJhbWV0ZXJfTWl4KHJhdGUgPSBjKDEsMSkpLA0KICAgICAgICAgICAgICAgIHNldEdyaWRQYXJhbU1peCA9IHNldEdyaWRQYXJhbWV0ZXJfTWl4KG1pbl9hbHBoYSA9IDFlLTEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9hbHBoYSA9IDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX2JldGEgPSAxZS0xMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYmV0YSA9IDEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fYWxwaGEgPSAyMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2JldGEgPSAyMCkpDQpgYGANCg0KPiBUaGUgbWVhbiBzcXVhcmUgZXJyb3JzIGV2YWx1YXRlZCBvbiAkMjBcJSQtdGVzdGluZyBkYXRhIG9mIHRoZSBhYm92ZSBjb21wdXRhdGlvbiBhcmUgcmVwb3J0ZWQgYmVsb3cuDQoNCmBgYHtyfQ0KcmYxIDwtIHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KFJpbmdzIH4gLiwgZGF0YSA9IGRmW3RyYWluMSwyOm5jb2woZGYpXSkNCmtmYzEkcHJlZGljdF9maW5hbCAlPiUNCiAgbXV0YXRlKHJmID0gcHJlZGljdChyZjEsIG5ld2RhdGEgPSBkZlshdHJhaW4xLDI6bmNvbChkZildKSkgJT4lDQogIHN3ZWVwKE1BUkdJTiA9IDEsIFNUQVRTID0gZGYkUmluZ3NbIXRyYWluMV0sIEZVTiA9ICItIikgJT4lDQogIC5eMiAgJT4lDQogIGNvbE1lYW5zDQpgYGANCg0KLS0tDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+JiMxMjgyMTQ7IFJlYWQgYWxzbyBbS2VybmVsQWdnUmVnXShcZmlsZXNcS2VybmVsQWdnUmVnLmh0bWwpIGFuZCBbTWl4Q29icmFSZWddKFxmaWxlc1xNaXhDb2JyYVJlZy5odG1sKTwvc3Bhbj4uDQoNCi0tLQ0KDQotIFtIYXMgZXQgYWwuICgyMDIxKV0oaHR0cHM6Ly93d3cudGFuZGZvbmxpbmUuY29tL2VwcmludC9ZS0dTOEdUS0RCS1lGWEVHRldTQi9mdWxsP3RhcmdldD0xMC4xMDgwLzAwOTQ5NjU1LjIwMjEuMTg5MTUzOSkNCi0gW0Zpc2NoZXIgYW5kIE1vdWdlb3QgKDIwMTkpXShodHRwczovL3d3dy5zY2llbmNlZGlyZWN0LmNvbS9zY2llbmNlL2FydGljbGUvcGlpL1MwMzc4Mzc1ODE4MzAyMzQ5KQ0KLSBbSGFzICgyMDIxKV0oaHR0cHM6Ly9oYWwuYXJjaGl2ZXMtb3V2ZXJ0ZXMuZnIvaGFsLTAyODg0MzMzdjUpDQotIFtCaWF1IGV0IGFsLiAoMjAxNildKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAwNDcyNTlYMTUwMDA5NTApDQotIC4uLg0KLSBbZHBseXIgdmlkZW9zXShodHRwczovL3d3dy55b3V0dWJlLmNvbS9oYXNodGFnL2RwbHlyKSBgciBmb250YXdlc29tZTo6ZmEoInZpZGVvIilgDQotIFtnZ3Bsb3QyIHZpZGVvIHR1dG9yaWFsXShodHRwczovL3d3dy55b3V0dWJlLmNvbS9oYXNodGFnL2dncGxvdDIpIGByIGZvbnRhd2Vzb21lOjpmYSgidmlkZW8iKWANCi0gW1IgZm9yIGRhdGEgc2NpZW5jZV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei8pDQoNCi0tLQ==